<commit_before>var util = require('util'),
    Marshal = require('./marshal'),
    Column = require('./column'),
    Row = require('./row'),
    ttype = require('./cassandra/1.0/cassandra_types');

/**
 * NO-Operation for deault callbacks
 * @private
 * @memberOf ColumnFamily
 */
var NOOP = function(){};

/**
 * Default read consistency level
 * @private
 * @constant
 * @memberOf ColumnFamily
 */
var DEFAULT_READ_CONSISTENCY = ttype.ConsistencyLevel.QUORUM;

/**
 * Default write consistency level
 * @private
 * @constant
 * @memberOf ColumnFamily
 */
var DEFAULT_WRITE_CONSISTENCY = ttype.ConsistencyLevel.QUORUM;

/**
 * A convenience method to normalize the standard parameters used by
 * a thrift operation. The parameter list must contain a `key` parameter
 * as it's first item.  The `column`, `subcolumn`, `options`, and
 * `callback` parameters are optional.
 * @param {Array} list The list of parameters
 * @private
 * @memberOf ColumnFamily
 * @returns {Object} a normalized version of the provided parameter values
 */
function normalizeParameters(list) {
  var args = { };
  args.key = list.shift();
  for(var i = 0; i < list.length; i++) {
    switch(typeof list[i]) {
      case 'function':
        args.callback = list[i];
        break;
      case 'object':
        args.options = list[i];
        break;
      default:
        if(i == 0) args.column = list[i];
        if(i == 1) args.subcolumn = list[i];
    }
  }
  return args;
}

/**
 * Gets an array of columns from an object
 * @param {Object} columns
 * @private
 * @memberOf ColumnFamily
 * @returns {Array} and array of columns
 */
function getColumns(columns){
  var keys = Object.keys(columns), len = keys.length, i = 0, key, value, arr = [],
      ts = new Date();
  for(; i < len; i += 1){
    key = keys[i];
    value = columns[key];

    if(value === null || value === undefined){
      value = '';
    }

    arr.push(new Column(key, value, ts));
  }
  return arr;
}

/**
 * Gets a slcie predicate based on some options
 * @private
 * @memberOf ColumnFamily
 * @returns {SlicePredicate}
 */
function getSlicePredicate(options, serializer){
  var predicate = new ttype.SlicePredicate();

  if(Array.isArray(options.columns)){
    var cols = [], i = 0, len = options.columns.length;
    for(; i < len; i += 1){
      cols.push( serializer.serialize(options.columns[i]) );
    }
    predicate.column_names = cols;
  } else {
    predicate.slice_range = new ttype.SliceRange({
      start:options.start,
      finish:options.end,
      reversed:options.reversed,
      count:options.max
    });
  }

  return predicate;
}

/**
 * Representation of a Column Family
 *
 * @param {Object} definition The Column Family definition
 * @constructor
 */
var ColumnFamily = function(keyspace, definition){
//  ttype.CfDef.call(this, definition);
  this.isSuper = definition.column_type === 'Super';
  this.keyspace = keyspace;
  this.connection = keyspace.connection;
  this.definition = definition;
  this.name = definition.name;
  this.columnMarshaller = new Marshal(definition.comparator_type);
  this.subcolumnMarshaller = this.isSuper ? new Marshal(definition.subcomparator_type) : null;
  this.valueMarshaller = new Marshal(definition.default_validation_class);
  this.keyMarshaller = new Marshal(definition.key_validation_class);
  var self = this;

  this.parent = new ttype.ColumnParent({
    column_family: this.name
  });

  this.columns = {};

  if(definition.column_metadata && Array.isArray(definition.column_metadata)){
    var i = 0, len = definition.column_metadata.length, col;
    for(; i < len; i += 1){
      col = definition.column_metadata[i];
      this.columns[col.name] = new Marshal(col.validation_class);
    }
  }

  /**
   * Returns a column parent
   * When calculating the column parent of a standard column family,
   * the parent is simply the column family name.  When dealing with
   * super columns on the other hand, an optional name parameter may
   * be provided.
   *
   * @param {Object} name The name of the column (optional)
   * @private
   * @memberOf ColumnFamily
   * @returns {Object} a Thrift ColumnParent object
   */
  function columnParent(column) {
    var args = {column_family: self.name};
    if(self.isSuper && column) {
      args.super_column = self.columnMarshaller.serialize(column);
    }
    return new ttype.ColumnParent(args);
  }

  /**
   * Returns a column path
   * As with the ColumnParent, the value of the ColumnPath depends on whether
   * this is a standard or super column family.  Both must specify the column
   * family name.  A standard column family may provide an optional column name
   * parameter.  In addition to the column name, a super column family may also
   * use a subcolumn parameter.
   *
   * @param {Object} column The name of the column (optional)
   * @param {Object} subcolumn The name of the subcolumn (optional)
   * @private
   * @memberOf ColumnFamily a Thrift ColumnPath object
   */
  function columnPath(column, subcolumn) {
    if(column === undefined) column = null;
    if(subcolumn === undefined) subcolumn = null;

    var args = {column_family: self.name};

    if(self.isSuper) {
      if(column) args.column = self.columnMarshaller.serialize(column);
      if(subcolumn) args.subcolumn = self.subcolumnMarshaller.serialize(subcolumn);
    }
    else {
      if(column) args.column = self.columnMarshaller.serialize(column);
    }
    return new ttype.ColumnPath(args);
  }

  /**
   * Remove a single row or column
   * This function uses a variable-length paramter list.  Which parameters
   * are passed depends on which column path should be used for the
   * removal and whether this column family is a super column or not.
   *
   * @param {String} key The key for this row (required)
   * @param {Object} column The column name (optional)
   * @param {Object} subcolumn The subcolumn name (optional)
   * @param {Object} options The thrift options for this operation (optional)
   * @param {Function} callback The callback to call once complete (optional)
   */
  this.remove = function() {
    var args = normalizeParameters(Array.prototype.slice.apply(arguments));
    args.callback = args.callback || NOOP;
    args.options = args.options || { };

    var marshalledKey = this.keyMarshaller.serialize(args.key).toString('binary'),
        path = columnPath(args.column, args.subcolumn),
        timestamp = args.options.timestamp || new Date(),
        consistency = args.options.consistency || DEFAULT_WRITE_CONSISTENCY;

    this.connection.execute('remove', marshalledKey, path, timestamp, consistency, args.callback);
  };

};

/**
 * Performs a set command to the cluster
 *
 * @param {String} key The key for the row
 * @param {Object} columns The value for the columns as represented by JSON or an array of Column objects
 * @param {Object} options The options for the insert
 * @param {Function} callback The callback to call once complete
 */
ColumnFamily.prototype.insert = function(key, columns, options, callback){
  if (typeof options === 'function'){
    callback = options;
    options = {};
  }

  if(!Array.isArray(columns)){
    columns = getColumns(columns);
  }

  var len = columns.length, i = 0, valueMarshaller, col,
      mutations = [], batch = {},
      consistency = options.consistency || DEFAULT_WRITE_CONSISTENCY;

  for(; i < len; i += 1){
    col = columns[i];
    valueMarshaller = this.columns[col.name] || this.valueMarshaller;
    mutations.push(new ttype.Mutation({
      column_or_supercolumn: new ttype.ColumnOrSuperColumn({
        column: col.toThrift(this.columnMarshaller, valueMarshaller)
      })
    }));
  }

  var marshalledKey = this.keyMarshaller.serialize(key).toString('binary');

  batch[marshalledKey] = {};
  batch[marshalledKey][this.definition.name] = mutations;

  this.connection.execute('batch_mutate', batch, consistency, callback);
};

/**
 * Get a row by its key
 * @param {String} key The key to get
 * @param {Object} options Options for the get, can have start, end, max, consistencyLevel
 *

 *
start: the from part of the column name

 *
end: the to part of the column name

 *
max: the max amount of columns to return

 *
columns: an {Array} of column names to get

 *
consistencyLevel: the read consistency level

 *

 * @param {Function} callback The callback to invoke once the response has been received
 */
ColumnFamily.prototype.get = function(key, options, callback){
  if (typeof options === 'function'){
    callback = options;
    options = {};
  }

  callback = callback || NOOP;

  var self = this,
      consistency = options.consistencyLevel || DEFAULT_READ_CONSISTENCY,
      marshalledKey = this.keyMarshaller.serialize(key).toString('binary'),
      predicate = getSlicePredicate(options, this.columnMarshaller);

  function onComplete(err, val){
    if(err){
      callback(err);
      return;
    }

    callback(null, Row.fromThrift(key, val, self));
  }

  this.connection.execute('get_slice', marshalledKey, this.parent, predicate, consistency, onComplete);
};

/**
 * Gets rows by their indexed fields
 * @param {Object} query Options for the rows part of the get
 *

 *
fields: an array of objects thjat contain { column:column_name, operator: 'EQ', value:value }
 *

 *
column: {String} The name of the column with the index

 *
operator: {String} The operator to use, can be EQ, GTE, GT, LTE, ot LT

 *
value: {String} The value to query by

 *

 *

 *
start: the start key to get

 *
max: the total amount of rows to return

 *

 * @param {Object} options Options for the get, can have start, end, max, consistencyLevel
 *

 *
start: the from part of the column name

 *
end: the to part of the column name

 *
max: the max amount of columns to return

 *
columns: an {Array} of column names to get

 *
consistencyLevel: the read consistency level

 *

 * @param {Function} callback The callback to invoke once the response has been received
 */
ColumnFamily.prototype.getIndexed = function(query, options, callback){
  if (typeof options === 'function'){
    callback = options;
    options = {};
  }

  callback = callback || NOOP;

  var self = this, indexClause, indexExpressions = [],
      i = 0, len = query.fields.length, field, valueMarshaller,
      consistency = options.consistencyLevel || DEFAULT_READ_CONSISTENCY,
      predicate = getSlicePredicate(options, this.columnMarshaller);

  for(; i < len; i += 1){
    field = query.fields[i];
    valueMarshaller = this.columns[field.column] || this.valueMarshaller;

    indexExpressions.push(new ttype.IndexExpression({
      column_name:this.columnMarshaller.serialize(field.column),
      op:ttype.IndexOperator[field.operator],
      value: valueMarshaller.serialize(field.value)
    }));
  }

  indexClause = new ttype.IndexClause({
    expressions: indexExpressions,
    start_key:query.start || '',
    count:query.max || 100
  });

  function onComplete(err, val){
    if(err){
      callback(err);
      return;
    }

    var results = [], i = 0, len = val.length, row;
    for(; i < len; i += 1){
      row = val[i];
      results.push(Row.fromThrift(row.key, row.columns, self));
    }
    callback(null, results);
  }

  this.connection.execute('get_indexed_slices', this.parent, indexClause, predicate, consistency, onComplete);
};

module.exports = ColumnFamily;
<commit_msg>Made remove a prototyped function.<commit_after>var util = require('util'),
    Marshal = require('./marshal'),
    Column = require('./column'),
    Row = require('./row'),
    ttype = require('./cassandra/1.0/cassandra_types');

/**
 * NO-Operation for deault callbacks
 * @private
 * @memberOf ColumnFamily
 */
var NOOP = function(){};

/**
 * Default read consistency level
 * @private
 * @constant
 * @memberOf ColumnFamily
 */
var DEFAULT_READ_CONSISTENCY = ttype.ConsistencyLevel.QUORUM;

/**
 * Default write consistency level
 * @private
 * @constant
 * @memberOf ColumnFamily
 */
var DEFAULT_WRITE_CONSISTENCY = ttype.ConsistencyLevel.QUORUM;

/**
 * Returns a column parent
 * When calculating the column parent of a standard column family,
 * the parent is simply the column family name.  When dealing with
 * super columns on the other hand, an optional name parameter may
 * be provided.
 *
 * @param {Object} cf A reference to the ColumnFamily
 * @param {Object} name The name of the column (optional)
 * @private
 * @memberOf ColumnFamily
 * @returns {Object} a Thrift ColumnParent object
 */
function columnParent(cf, column) {
  var args = {column_family: cf.name};
  if(cf.isSuper && column) {
    args.super_column = cf.columnMarshaller.serialize(column);
  }
  return new ttype.ColumnParent(args);
}

/**
 * Returns a column path
 * As with the ColumnParent, the value of the ColumnPath depends on whether
 * this is a standard or super column family.  Both must specify the column
 * family name.  A standard column family may provide an optional column name
 * parameter.  In addition to the column name, a super column family may also
 * use a subcolumn parameter.
 *
 * @param {Object} cf A reference to the ColumnFamily
 * @param {Object} column The name of the column (optional)
 * @param {Object} subcolumn The name of the subcolumn (optional)
 * @private
 * @memberOf ColumnFamily a Thrift ColumnPath object
 */
function columnPath(cf, column, subcolumn) {
  if(column === undefined){column = null;}
  if(subcolumn === undefined){subcolumn = null;}

  var args = {column_family: cf.name};

  if(cf.isSuper) {
    if(column){args.column = cf.columnMarshaller.serialize(column);}
    if(subcolumn){args.subcolumn = cf.subcolumnMarshaller.serialize(subcolumn);}
  }
  else {
    if(column){args.column = cf.columnMarshaller.serialize(column);}
  }
  return new ttype.ColumnPath(args);
}

/**
 * A convenience method to normalize the standard parameters used by
 * a thrift operation. The parameter list must contain a `key` parameter
 * as it's first item.  The `column`, `subcolumn`, `options`, and
 * `callback` parameters are optional.
 * @param {Array} list The list of parameters
 * @private
 * @memberOf ColumnFamily
 * @returns {Object} a normalized version of the provided parameter values
 */
function normalizeParameters(list) {
  var args = { };
  args.key = list.shift();
  for(var i = 0; i < list.length; i++) {
    switch(typeof list[i]) {
      case 'function':
        args.callback = list[i];
        break;
      case 'object':
        args.options = list[i];
        break;
      default:
        if(i === 0){args.column = list[i];}
        if(i === 1){args.subcolumn = list[i];}
        break;
    }
  }
  return args;
}

/**
 * Gets an array of columns from an object
 * @param {Object} columns
 * @private
 * @memberOf ColumnFamily
 * @returns {Array} and array of columns
 */
function getColumns(columns){
  var keys = Object.keys(columns), len = keys.length, i = 0, key, value, arr = [],
      ts = new Date();
  for(; i < len; i += 1){
    key = keys[i];
    value = columns[key];

    if(value === null || value === undefined){
      value = '';
    }

    arr.push(new Column(key, value, ts));
  }
  return arr;
}

/**
 * Gets a slcie predicate based on some options
 * @private
 * @memberOf ColumnFamily
 * @returns {SlicePredicate}
 */
function getSlicePredicate(options, serializer){
  var predicate = new ttype.SlicePredicate();

  if(Array.isArray(options.columns)){
    var cols = [], i = 0, len = options.columns.length;
    for(; i < len; i += 1){
      cols.push( serializer.serialize(options.columns[i]) );
    }
    predicate.column_names = cols;
  } else {
    predicate.slice_range = new ttype.SliceRange({
      start:options.start,
      finish:options.end,
      reversed:options.reversed,
      count:options.max
    });
  }

  return predicate;
}

/**
 * Representation of a Column Family
 *
 * @param {Object} definition The Column Family definition
 * @constructor
 */
var ColumnFamily = function(keyspace, definition){
//  ttype.CfDef.call(this, definition);
  this.isSuper = definition.column_type === 'Super';
  this.keyspace = keyspace;
  this.connection = keyspace.connection;
  this.definition = definition;
  this.name = definition.name;
  this.columnMarshaller = new Marshal(definition.comparator_type);
  this.subcolumnMarshaller = this.isSuper ? new Marshal(definition.subcomparator_type) : null;
  this.valueMarshaller = new Marshal(definition.default_validation_class);
  this.keyMarshaller = new Marshal(definition.key_validation_class);
  var self = this;

  this.parent = new ttype.ColumnParent({
    column_family: this.name
  });

  this.columns = {};

  if(definition.column_metadata && Array.isArray(definition.column_metadata)){
    var i = 0, len = definition.column_metadata.length, col;
    for(; i < len; i += 1){
      col = definition.column_metadata[i];
      this.columns[col.name] = new Marshal(col.validation_class);
    }
  }

};

/**
 * Performs a set command to the cluster
 *
 * @param {String} key The key for the row
 * @param {Object} columns The value for the columns as represented by JSON or an array of Column objects
 * @param {Object} options The options for the insert
 * @param {Function} callback The callback to call once complete
 */
ColumnFamily.prototype.insert = function(key, columns, options, callback){
  if (typeof options === 'function'){
    callback = options;
    options = {};
  }

  if(!Array.isArray(columns)){
    columns = getColumns(columns);
  }

  var len = columns.length, i = 0, valueMarshaller, col,
      mutations = [], batch = {},
      consistency = options.consistency || DEFAULT_WRITE_CONSISTENCY;

  for(; i < len; i += 1){
    col = columns[i];
    valueMarshaller = this.columns[col.name] || this.valueMarshaller;
    mutations.push(new ttype.Mutation({
      column_or_supercolumn: new ttype.ColumnOrSuperColumn({
        column: col.toThrift(this.columnMarshaller, valueMarshaller)
      })
    }));
  }

  var marshalledKey = this.keyMarshaller.serialize(key).toString('binary');

  batch[marshalledKey] = {};
  batch[marshalledKey][this.definition.name] = mutations;

  this.connection.execute('batch_mutate', batch, consistency, callback);
};

/**
 * Remove a single row or column
 * This function uses a variable-length paramter list.  Which parameters
 * are passed depends on which column path should be used for the
 * removal and whether this column family is a super column or not.
 *
 * @param {String} key The key for this row (required)
 * @param {Object} column The column name (optional)
 * @param {Object} subcolumn The subcolumn name (optional)
 * @param {Object} options The thrift options for this operation (optional)
 * @param {Function} callback The callback to call once complete (optional)
 */
ColumnFamily.prototype.remove = function() {
  var args = normalizeParameters(Array.prototype.slice.apply(arguments));
  args.callback = args.callback || NOOP;
  args.options = args.options || { };

  var self = this;
  var marshalledKey = this.keyMarshaller.serialize(args.key).toString('binary'),
      path = columnPath(self, args.column, args.subcolumn),
      timestamp = args.options.timestamp || new Date(),
      consistency = args.options.consistency || DEFAULT_WRITE_CONSISTENCY;

  this.connection.execute('remove', marshalledKey, path, timestamp, consistency, args.callback);
};

/**
 * Get a row by its key
 * @param {String} key The key to get
 * @param {Object} options Options for the get, can have start, end, max, consistencyLevel
 *

 *
start: the from part of the column name

 *
end: the to part of the column name

 *
max: the max amount of columns to return

 *
columns: an {Array} of column names to get

 *
consistencyLevel: the read consistency level

 *

 * @param {Function} callback The callback to invoke once the response has been received
 */
ColumnFamily.prototype.get = function(key, options, callback){
  if (typeof options === 'function'){
    callback = options;
    options = {};
  }

  callback = callback || NOOP;

  var self = this,
      consistency = options.consistencyLevel || DEFAULT_READ_CONSISTENCY,
      marshalledKey = this.keyMarshaller.serialize(key).toString('binary'),
      predicate = getSlicePredicate(options, this.columnMarshaller);

  function onComplete(err, val){
    if(err){
      callback(err);
      return;
    }

    callback(null, Row.fromThrift(key, val, self));
  }

  this.connection.execute('get_slice', marshalledKey, this.parent, predicate, consistency, onComplete);
};

/**
 * Gets rows by their indexed fields
 * @param {Object} query Options for the rows part of the get
 *

 *
fields: an array of objects thjat contain { column:column_name, operator: 'EQ', value:value }
 *

 *
column: {String} The name of the column with the index

 *
operator: {String} The operator to use, can be EQ, GTE, GT, LTE, ot LT

 *
value: {String} The value to query by

 *

 *

 *
start: the start key to get

 *
max: the total amount of rows to return

 *

 * @param {Object} options Options for the get, can have start, end, max, consistencyLevel
 *

 *
start: the from part of the column name

 *
end: the to part of the column name

 *
max: the max amount of columns to return

 *
columns: an {Array} of column names to get

 *
consistencyLevel: the read consistency level

 *

 * @param {Function} callback The callback to invoke once the response has been received
 */
ColumnFamily.prototype.getIndexed = function(query, options, callback){
  if (typeof options === 'function'){
    callback = options;
    options = {};
  }

  callback = callback || NOOP;

  var self = this, indexClause, indexExpressions = [],
      i = 0, len = query.fields.length, field, valueMarshaller,
      consistency = options.consistencyLevel || DEFAULT_READ_CONSISTENCY,
      predicate = getSlicePredicate(options, this.columnMarshaller);

  for(; i < len; i += 1){
    field = query.fields[i];
    valueMarshaller = this.columns[field.column] || this.valueMarshaller;

    indexExpressions.push(new ttype.IndexExpression({
      column_name:this.columnMarshaller.serialize(field.column),
      op:ttype.IndexOperator[field.operator],
      value: valueMarshaller.serialize(field.value)
    }));
  }

  indexClause = new ttype.IndexClause({
    expressions: indexExpressions,
    start_key:query.start || '',
    count:query.max || 100
  });

  function onComplete(err, val){
    if(err){
      callback(err);
      return;
    }

    var results = [], i = 0, len = val.length, row;
    for(; i < len; i += 1){
      row = val[i];
      results.push(Row.fromThrift(row.key, row.columns, self));
    }
    callback(null, results);
  }

  this.connection.execute('get_indexed_slices', this.parent, indexClause, predicate, consistency, onComplete);
};

module.exports = ColumnFamily;
<|endoftext|>


<commit_before>/*global angular:true */
angular.module('allure.core.controllers', [])
    .controller('OverviewCtrl', function($scope, $storage, orderByFilter, status, percents, overview, defects, testsuites, widgets) {
        "use strict";
        $scope.overview = overview;
        $scope.defects = defects.defectsList.filter(function(defect) {
            return defect.defects && defect.defects.length > 0;
        });
        $scope.defects.forEach(function(defect) {
            defect.defects = orderByFilter(defect.defects, function(defect) {
                return defect.testCases.length;
            }, true);
        });
        $scope.statistic = testsuites.testSuites.reduce(function(statistic, testsuite) {
            ['passed', 'pending', 'canceled', 'broken', 'failed', 'total'].forEach(function(status) {
                statistic[status] += testsuite.statistic[status];
            });
            return statistic;
        }, {
            passed: 0, pending: 0, canceled: 0, failed: 0, broken: 0, total: 0
        });

        $scope.percents = percents($scope.statistic);

        var store = $storage('allure-widgets'),
            storedWidgets = store.getItem('widgets') || widgets.reduce(function(all, widget, index) {
            all[index % 2].push(widget.name);
            return all;
        }, [[], []]);

        $scope.widgets = storedWidgets.map(function(col) {
            return col.map(function(widgetName) {
                return widgets.filter(function(widget) {
                    return widget.name === widgetName;
                })[0];
            })
        });

        $scope.onSort = function() {
            store.setItem('widgets', $scope.widgets.map(function(col) {
                return col.map(function(widget) {
                    return widget.name;
                });
            }));
        };

        $scope.sortableConfig = {
            group: 'widgets',
            handle: ".widget_handle",
            ghostClass: "widget-dragged",
            onEnd: $scope.onSort
        };
    })

    .controller('NavbarCtrl', function($scope, $window, $http, $storage, $translate) {
        'use strict';
        function browserLanguage() {
            return $window.navigator.language && $window.navigator.language.split('-').shift();
        }
        var locale = $storage('locale');
        $scope.setLang = function(langKey) {
            $scope.selectedLang = langKey.locale;
            $translate.use($scope.selectedLang);
            locale.setItem('lang', $scope.selectedLang);
        };

        $scope.langs = [{
            name: "ENG",
            locale: "en"
        }, {
            name: "РУС",
            locale: "ru"
        }];
        $scope.selectedLang = locale.getItem('lang') || browserLanguage() || 'en';
        $translate.use($scope.selectedLang);

        $http.get('data/report.json').then(function(response) {
            $scope.report = response.data;
        });
    })

    .controller('TabsController', function($scope, $state, $storage, allureTabs) {
        'use strict';
        var settings = $storage('settings');

        $scope.tabs = allureTabs;
        $scope.isCollapsed = function() {
            return settings.getItem('collapsed');
        };

        $scope.toggleCollapsed = function() {
            settings.setItem('collapsed', !$scope.isCollapsed());
        };

        $scope.isCurrent = function(state) {
            return $state.includes(state);
        };
    });
<commit_msg>remove unused functions from controler<commit_after>/*global angular:true */
angular.module('allure.core.controllers', [])
    .controller('OverviewCtrl', function($scope, $storage, widgets) {
        "use strict";

        var store = $storage('allure-widgets'),
            storedWidgets = store.getItem('widgets') || widgets.reduce(function(all, widget, index) {
            all[index % 2].push(widget.name);
            return all;
        }, [[], []]);

        $scope.widgets = storedWidgets.map(function(col) {
            return col.map(function(widgetName) {
                return widgets.filter(function(widget) {
                    return widget.name === widgetName;
                })[0];
            })
        });

        $scope.onSort = function() {
            store.setItem('widgets', $scope.widgets.map(function(col) {
                return col.map(function(widget) {
                    return widget.name;
                });
            }));
        };

        $scope.sortableConfig = {
            group: 'widgets',
            handle: ".widget_handle",
            ghostClass: "widget-dragged",
            onEnd: $scope.onSort
        };
    })

    .controller('NavbarCtrl', function($scope, $window, $http, $storage, $translate) {
        'use strict';
        function browserLanguage() {
            return $window.navigator.language && $window.navigator.language.split('-').shift();
        }
        var locale = $storage('locale');
        $scope.setLang = function(langKey) {
            $scope.selectedLang = langKey.locale;
            $translate.use($scope.selectedLang);
            locale.setItem('lang', $scope.selectedLang);
        };

        $scope.langs = [{
            name: "ENG",
            locale: "en"
        }, {
            name: "РУС",
            locale: "ru"
        }];
        $scope.selectedLang = locale.getItem('lang') || browserLanguage() || 'en';
        $translate.use($scope.selectedLang);

        $http.get('data/report.json').then(function(response) {
            $scope.report = response.data;
        });
    })

    .controller('TabsController', function($scope, $state, $storage, allureTabs) {
        'use strict';
        var settings = $storage('settings');

        $scope.tabs = allureTabs;
        $scope.isCollapsed = function() {
            return settings.getItem('collapsed');
        };

        $scope.toggleCollapsed = function() {
            settings.setItem('collapsed', !$scope.isCollapsed());
        };

        $scope.isCurrent = function(state) {
            return $state.includes(state);
        };
    });
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react'
import { Map, TileLayer, ScaleControl } from 'react-leaflet'
import { Circle } from 'leaflet'
import SpritesLayer from './sprites-layer'
import EarthquakePopup from './earthquake-popup'
import VolcanoPopup from './volcano-popup'
import PlateBoundariesLayer from './plate-boundaries-layer'
import PlateArrowsLayer from './plate-arrows-layer'
import LabelsLayer from './labels-layer'
import PinsLayer from './pins-layer'
import PlateMovementLayer from './plate-movement-layer'
import CrossSectionDrawLayer from './cross-section-draw-layer'
import addTouchSupport from '../custom-leaflet/touch-support'
import { mapLayer } from '../map-layer-tiles'
import log from '../logger'
import plateNames from '../data/plate-names'
import continentOceanNames from '../data/continent-ocean-names'
import config from '../config'

import '../../css/leaflet/leaflet.css'
import '../../css/seismic-eruptions-map.less'

const INITIAL_BOUNDS = [
  [config.minLat, config.minLng],
  [config.maxLat, config.maxLng]
]

const INITIAL_CENTER = { lat: config.centerLat, lng: config.centerLng }

// It delays download of earthquakes data on map moveend event, so user can pan or zoom map
// a few times quickly before the download starts.
const BOUNDS_UPDATE_DELAY = 600 // ms

const DEFAULT_MAX_ZOOM = 13

// let scaleWidth = 0
// let scaleHeight = 0

// Leaflet map doesn't support custom touch events by default.
addTouchSupport()

export default class SeismicEruptionsMap extends PureComponent {
  constructor (props) {
    super(props)
    this.state = {
      selectedEarthquake: null,
      selectedVolcano: null
    }
    this.handleEarthquakeClick = this.handleEarthquakeClick.bind(this)
    this.handleEarthquakePopupClose = this.handleEarthquakePopupClose.bind(this)
    this.handleVolcanoClick = this.handleVolcanoClick.bind(this)
    this.handleVolcanoPopupClose = this.handleVolcanoPopupClose.bind(this)
    this.handleMapViewportChanged = this.handleMapViewportChanged.bind(this)
    this.handleInitialBoundsSetup = this.handleInitialBoundsSetup.bind(this)
    this.handleZoom = this.handleZoom.bind(this)
    this.handlePinUpdated = this.handlePinUpdated.bind(this)
  }

  get map () {
    if (this.refs.map) {
      return this.refs.map.leafletElement
    }
  }

  get mapRegion () {
    const bounds = this.map.getBounds()
    return {
      minLng: bounds.getWest(),
      maxLng: bounds.getEast(),
      minLat: bounds.getSouth(),
      maxLat: bounds.getNorth()
    }
  }

  get mapZoom () {
    if (this.map) {
      return this.map.getZoom()
    }
  }

  get baseLayer () {
    const { layers } = this.props
    return mapLayer(layers.get('base'))
  }

  latLngToPoint (latLng) {
    return this.map.latLngToContainerPoint(latLng)
  }

  componentDidMount () {
    // Make sure that SVG overlay layer exists from the very beginning. It's created dynamically when the first
    // SVG object is added to the map. It solves / simplifies some issues. For example we don't want earthquakes
    // to be clickable when we enter cross-section drawing mode. The simplest solution is to set Canvas z-index
    // lower than SVG z-index, but it won't work if the SVG layer hasn't been created yet.
    createSVGOverlayLayer(this.map)
    // Initially Leaflet always triggers mapmove event, as probably real bounds are a bit different than
    // provided bounds (due to screen size etc.). That is causing mark2DViewModified to be called and reset view icon
    // being shown. This is unwanted, as there has been no user interaction. Mark this view unmodified again.
    const { mark2DViewModified, setMapStatus } = this.props
    mark2DViewModified(false)
    setMapStatus(this.mapRegion, this.mapZoom)

    this.map.on('click', (e) => {
      this.handleMapClick(e)
    })
    if (!config.sizeToFitBounds) {
      // if we are not using bounds, and instead using a center point, force a refresh of the viewport to
      // get the earthquakes to render on first load
      this.handleMapViewportChanged()
    }
    this.calculateScale()
    window.addEventListener('resize', this.handleInitialBoundsSetup)
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleInitialBoundsSetup)
  }

  componentDidUpdate () {
    // This maxZoom option is not handled by react-leaftlet as a dynamic react property (it doesn't update after
    // Map component is created), so we need to use raw Leaflet API to dynamically change it.
    let maxZoom = config.zoomMax > -1 ? config.zoomMax : DEFAULT_MAX_ZOOM
    if (this.baseLayer.maxZoom && maxZoom > this.baseLayer.maxZoom) maxZoom = this.baseLayer.maxZoom
    this.refs.map.leafletElement.setMaxZoom(maxZoom)
  }

  // This method is called on window.resize. For some reason, when Seismic Explorer is embedded in iframe,
  // and this iframe size is updated by parent page, Leaflet gets lost and doesn't set initial bounds correctly.
  // Related PT story: https://www.pivotaltracker.com/n/projects/1929933/stories/167580182/comments/205160884
  // I can't explain why it happens, but that's the only reliable workaround I've found -> call Leaflet's
  // invalidateSize() method and then fit bounds again. Note that we do that ONLY when map hasn't been modified
  // the user explicitely. Otherwise, any resize of the window would cause the view to reset. It could be unexpected
  // and annoying to users.
  handleInitialBoundsSetup () {
    if (!this.props.mapModified) {
      this.map.invalidateSize()
      if (config.sizeToFitBounds) {
        this.fitBounds()
      }
    }
    this.calculateScale()
  }

  handleMapViewportChanged (e) {
    const { mark2DViewModified } = this.props
    mark2DViewModified(true)
    this._mapBeingDragged = false

    clearTimeout(this._boundsUpdateTimeoutID)
    this._boundsUpdateTimeoutID = setTimeout(() => {
      const { layers, setMapStatus } = this.props
      setMapStatus(this.mapRegion, this.mapZoom, layers.get('earthquakes'))

      const bounds = this.map.getBounds()

      log('MapRegionChanged', {
        minLat: bounds.getSouthWest().lat,
        minLng: bounds.getSouthWest().lng,
        maxLat: bounds.getNorthEast().lat,
        maxLng: bounds.getNorthEast().lng,
        zoom: this.mapZoom
      })
    }, BOUNDS_UPDATE_DELAY)
  }

  handleZoom (e) {
    // After zooming, if we are showing a scale, recalculate the properties
    this.calculateScale()
  }

  handleEarthquakeClick (event, earthquake) {
    // Do not open earthquake popup if click was part of the map dragging action.
    if (this._mapBeingDragged) return
    this.setState({ selectedEarthquake: earthquake })
    log('EarthquakeClicked', earthquake)
  }

  handleEarthquakePopupClose () {
    this.setState({ selectedEarthquake: null })
  }

  handleVolcanoClick (event, volcano) {
    if (this._mapBeingDragged) return
    this.setState({ selectedVolcano: volcano })
    log('Volcano Clicked', volcano)
  }

  handleVolcanoPopupClose () {
    this.setState({ selectedVolcano: null })
  }

  handleMapClick (e) {
    console.log('lat:', e.latlng.lat, 'lng:', e.latlng.lng)

    const { pins } = this.props
    // Note that this doesn't yet work if we need to move more than one pin. In future, we could
    // extend to enable click-to-add more pins
    const maxPins = 1
    if (config.clickToMoveSinglePin) {
      const pinList = pins.toJS()
      if (pinList.length < maxPins) {
        this.props.setPin(pinList.length, e.latlng, config.defaultPinLabel)
      } else {
        // update the position of the last-placed pin
        this.props.updatePin(pinList.length - 1, e.latlng)
      }
    }
  }
  handlePinUpdated (idx, markerElement, latLng) {
    // update state with marker from Leaflet
    this.props.updatePin(idx, latLng, markerElement)
  }

  calculateScale () {
    // const bounds = this.map.getBounds()
    // const distWidthM = bounds.getSouthWest().distanceTo(bounds.getSouthEast())
    // const distHeightM = bounds.getNorthEast().distanceTo(bounds.getSouthEast())
    // // Note that these distances _should_ be in meters, but at zoom levels 3 and lower, at larger screen sizes
    // // this calculation can quietly return values in km, so caution should be used if required at zoom 3 or lower.
    // const widthKm = Math.round(distWidthM / 1000) // m -> km
    // const heightKm = Math.round(distHeightM / 1000) // m -> km
    // scaleWidth = widthKm.toLocaleString(navigator.language, { minimumFractionDigits: 0 })
    // scaleHeight = heightKm.toLocaleString(navigator.language, { minimumFractionDigits: 0 })
  }

  fitBounds (bounds = INITIAL_BOUNDS) {
    const { mark2DViewModified } = this.props
    this.map.fitBounds(bounds)
    mark2DViewModified(false)
    // Reset this flag again after some time, as Leaflet will call view updated event at the end of animation.
    setTimeout(() => mark2DViewModified(false), 400)
    log('ResetMapClicked')
  }

  render () {
    const { mode, earthquakes, volcanoes, layers, crossSectionPoints, mapStatus, setCrossSectionPoint, pins } = this.props
    const { selectedEarthquake, selectedVolcano } = this.state
    const baseLayer = this.baseLayer
    let url = baseLayer.url
    if (baseLayer.url.indexOf('{c}') > -1) {
      // If screen scaled display has higher than 1 pixel ratio, request scaled tiles, otherwise just request regular tiles
      // Since devicePixelRatio can change between displays and we can't guarantee how many versions of the base tiles there are available,
      // limit the request scaling to 2x if the user has any scale of higher dpi display.
      // There's no visible issue in requesting 2x on a regular dpi display aside from bandwidth
      url = window.devicePixelRatio && window.devicePixelRatio !== 1 ? baseLayer.url.replace('{c}', '@2x') : baseLayer.url.replace('{c}', '')
    }

    const allowDragging = config.allowDrag
    const allowFreeMouseZoom = config.sizeToFitBounds ? true : 'center'
    const bounds = config.sizeToFitBounds ? INITIAL_BOUNDS : undefined
    const center = config.sizeToFitBounds ? undefined : INITIAL_CENTER
    const zoom = this.map ? this.mapZoom : config.centeredInitialZoom

    const showZoomControls = config.minZoom !== config.maxZoom

    return (
      <div className={`seismic-eruptions-map mode-${mode}`}>
        <Map ref='map' className='map'
          onViewportChanged={this.handleMapViewportChanged}
          bounds={bounds}
          minZoom={config.zoomMin > -1 ? config.zoomMin : 2}
          dragging={allowDragging}
          zoom={zoom}
          center={center}
          doubleClickZoom={allowFreeMouseZoom}
          scrollWheelZoom={allowFreeMouseZoom}
          onzoomend={this.handleZoom}
          zoomControl={showZoomControls}
        >
          {/* #key attribute is very important here. #subdomains is not a dynamic property, so we can't reuse the same */}
          {/* component instance when we switch between maps with subdomains and without. */}
          <TileLayer key={baseLayer.type} url={url} subdomains={baseLayer.subdomains} attribution={baseLayer.attribution} />
          {layers.get('plateBoundaries') && <PlateBoundariesLayer mapRegion={mapStatus.get('region')} mapZoom={mapStatus.get('zoom')} />}
          {layers.get('continentOceanNames') && <LabelsLayer mapRegion={mapStatus.get('region')} labels={continentOceanNames} />}
          {layers.get('plateNames') && <LabelsLayer mapRegion={mapStatus.get('region')} labels={plateNames} />}
          {layers.get('plateArrows') && <PlateArrowsLayer mapRegion={mapStatus.get('region')} />}
          {layers.get('plateMovement') && <PlateMovementLayer />}
          {pins && <PinsLayer mapRegion={mapStatus.get('region')} pins={pins} onPinUpdated={this.handlePinUpdated} />}
          {mode !== '3d' &&
            /* Performance optimization. Update of this component is expensive. Remove it when the map is invisible. */
            <SpritesLayer earthquakes={earthquakes} volcanoes={volcanoes}
              onEarthquakeClick={this.handleEarthquakeClick} onVolcanoClick={this.handleVolcanoClick} />
          }
          {mode === '2d' && selectedEarthquake &&
            <EarthquakePopup earthquake={selectedEarthquake} onPopupClose={this.handleEarthquakePopupClose} />
          }
          {mode === '2d' && selectedVolcano &&
            <VolcanoPopup volcano={selectedVolcano} onPopupClose={this.handleVolcanoPopupClose} />
          }
          {mode === 'cross-section' &&
            <CrossSectionDrawLayer crossSectionPoints={crossSectionPoints} setCrossSectionPoint={setCrossSectionPoint} />
          }
          {!config.showUserInterface && showZoomControls &&
            <ScaleControl position={'topleft'} />
          }
        </Map>
        {!config.showUserInterface && showZoomControls &&

{`Zoom level: ${zoom}`}

        }

    )
  }
}

function createSVGOverlayLayer (map) {
  map.addLayer(new Circle([0, 0], 0, { opacity: 0, fillOpacity: 0 }))
}
<commit_msg>Fix parameter name<commit_after>import React, { PureComponent } from 'react'
import { Map, TileLayer, ScaleControl } from 'react-leaflet'
import { Circle } from 'leaflet'
import SpritesLayer from './sprites-layer'
import EarthquakePopup from './earthquake-popup'
import VolcanoPopup from './volcano-popup'
import PlateBoundariesLayer from './plate-boundaries-layer'
import PlateArrowsLayer from './plate-arrows-layer'
import LabelsLayer from './labels-layer'
import PinsLayer from './pins-layer'
import PlateMovementLayer from './plate-movement-layer'
import CrossSectionDrawLayer from './cross-section-draw-layer'
import addTouchSupport from '../custom-leaflet/touch-support'
import { mapLayer } from '../map-layer-tiles'
import log from '../logger'
import plateNames from '../data/plate-names'
import continentOceanNames from '../data/continent-ocean-names'
import config from '../config'

import '../../css/leaflet/leaflet.css'
import '../../css/seismic-eruptions-map.less'

const INITIAL_BOUNDS = [
  [config.minLat, config.minLng],
  [config.maxLat, config.maxLng]
]

const INITIAL_CENTER = { lat: config.centerLat, lng: config.centerLng }

// It delays download of earthquakes data on map moveend event, so user can pan or zoom map
// a few times quickly before the download starts.
const BOUNDS_UPDATE_DELAY = 600 // ms

const DEFAULT_MAX_ZOOM = 13

// let scaleWidth = 0
// let scaleHeight = 0

// Leaflet map doesn't support custom touch events by default.
addTouchSupport()

export default class SeismicEruptionsMap extends PureComponent {
  constructor (props) {
    super(props)
    this.state = {
      selectedEarthquake: null,
      selectedVolcano: null
    }
    this.handleEarthquakeClick = this.handleEarthquakeClick.bind(this)
    this.handleEarthquakePopupClose = this.handleEarthquakePopupClose.bind(this)
    this.handleVolcanoClick = this.handleVolcanoClick.bind(this)
    this.handleVolcanoPopupClose = this.handleVolcanoPopupClose.bind(this)
    this.handleMapViewportChanged = this.handleMapViewportChanged.bind(this)
    this.handleInitialBoundsSetup = this.handleInitialBoundsSetup.bind(this)
    this.handleZoom = this.handleZoom.bind(this)
    this.handlePinUpdated = this.handlePinUpdated.bind(this)
  }

  get map () {
    if (this.refs.map) {
      return this.refs.map.leafletElement
    }
  }

  get mapRegion () {
    const bounds = this.map.getBounds()
    return {
      minLng: bounds.getWest(),
      maxLng: bounds.getEast(),
      minLat: bounds.getSouth(),
      maxLat: bounds.getNorth()
    }
  }

  get mapZoom () {
    if (this.map) {
      return this.map.getZoom()
    }
  }

  get baseLayer () {
    const { layers } = this.props
    return mapLayer(layers.get('base'))
  }

  latLngToPoint (latLng) {
    return this.map.latLngToContainerPoint(latLng)
  }

  componentDidMount () {
    // Make sure that SVG overlay layer exists from the very beginning. It's created dynamically when the first
    // SVG object is added to the map. It solves / simplifies some issues. For example we don't want earthquakes
    // to be clickable when we enter cross-section drawing mode. The simplest solution is to set Canvas z-index
    // lower than SVG z-index, but it won't work if the SVG layer hasn't been created yet.
    createSVGOverlayLayer(this.map)
    // Initially Leaflet always triggers mapmove event, as probably real bounds are a bit different than
    // provided bounds (due to screen size etc.). That is causing mark2DViewModified to be called and reset view icon
    // being shown. This is unwanted, as there has been no user interaction. Mark this view unmodified again.
    const { mark2DViewModified, setMapStatus } = this.props
    mark2DViewModified(false)
    setMapStatus(this.mapRegion, this.mapZoom)

    this.map.on('click', (e) => {
      this.handleMapClick(e)
    })
    if (!config.sizeToFitBounds) {
      // if we are not using bounds, and instead using a center point, force a refresh of the viewport to
      // get the earthquakes to render on first load
      this.handleMapViewportChanged()
    }
    this.calculateScale()
    window.addEventListener('resize', this.handleInitialBoundsSetup)
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleInitialBoundsSetup)
  }

  componentDidUpdate () {
    // This maxZoom option is not handled by react-leaftlet as a dynamic react property (it doesn't update after
    // Map component is created), so we need to use raw Leaflet API to dynamically change it.
    let maxZoom = config.zoomMax > -1 ? config.zoomMax : DEFAULT_MAX_ZOOM
    if (this.baseLayer.maxZoom && maxZoom > this.baseLayer.maxZoom) maxZoom = this.baseLayer.maxZoom
    this.refs.map.leafletElement.setMaxZoom(maxZoom)
  }

  // This method is called on window.resize. For some reason, when Seismic Explorer is embedded in iframe,
  // and this iframe size is updated by parent page, Leaflet gets lost and doesn't set initial bounds correctly.
  // Related PT story: https://www.pivotaltracker.com/n/projects/1929933/stories/167580182/comments/205160884
  // I can't explain why it happens, but that's the only reliable workaround I've found -> call Leaflet's
  // invalidateSize() method and then fit bounds again. Note that we do that ONLY when map hasn't been modified
  // the user explicitely. Otherwise, any resize of the window would cause the view to reset. It could be unexpected
  // and annoying to users.
  handleInitialBoundsSetup () {
    if (!this.props.mapModified) {
      this.map.invalidateSize()
      if (config.sizeToFitBounds) {
        this.fitBounds()
      }
    }
    this.calculateScale()
  }

  handleMapViewportChanged (e) {
    const { mark2DViewModified } = this.props
    mark2DViewModified(true)
    this._mapBeingDragged = false

    clearTimeout(this._boundsUpdateTimeoutID)
    this._boundsUpdateTimeoutID = setTimeout(() => {
      const { layers, setMapStatus } = this.props
      setMapStatus(this.mapRegion, this.mapZoom, layers.get('earthquakes'))

      const bounds = this.map.getBounds()

      log('MapRegionChanged', {
        minLat: bounds.getSouthWest().lat,
        minLng: bounds.getSouthWest().lng,
        maxLat: bounds.getNorthEast().lat,
        maxLng: bounds.getNorthEast().lng,
        zoom: this.mapZoom
      })
    }, BOUNDS_UPDATE_DELAY)
  }

  handleZoom (e) {
    // After zooming, if we are showing a scale, recalculate the properties
    this.calculateScale()
  }

  handleEarthquakeClick (event, earthquake) {
    // Do not open earthquake popup if click was part of the map dragging action.
    if (this._mapBeingDragged) return
    this.setState({ selectedEarthquake: earthquake })
    log('EarthquakeClicked', earthquake)
  }

  handleEarthquakePopupClose () {
    this.setState({ selectedEarthquake: null })
  }

  handleVolcanoClick (event, volcano) {
    if (this._mapBeingDragged) return
    this.setState({ selectedVolcano: volcano })
    log('Volcano Clicked', volcano)
  }

  handleVolcanoPopupClose () {
    this.setState({ selectedVolcano: null })
  }

  handleMapClick (e) {
    console.log('lat:', e.latlng.lat, 'lng:', e.latlng.lng)

    const { pins } = this.props
    // Note that this doesn't yet work if we need to move more than one pin. In future, we could
    // extend to enable click-to-add more pins
    const maxPins = 1
    if (config.clickToMoveSinglePin) {
      const pinList = pins.toJS()
      if (pinList.length < maxPins) {
        this.props.setPin(pinList.length, e.latlng, config.defaultPinLabel)
      } else {
        // update the position of the last-placed pin
        this.props.updatePin(pinList.length - 1, e.latlng)
      }
    }
  }
  handlePinUpdated (idx, markerElement, latLng) {
    // update state with marker from Leaflet
    this.props.updatePin(idx, latLng, markerElement)
  }

  calculateScale () {
    // const bounds = this.map.getBounds()
    // const distWidthM = bounds.getSouthWest().distanceTo(bounds.getSouthEast())
    // const distHeightM = bounds.getNorthEast().distanceTo(bounds.getSouthEast())
    // // Note that these distances _should_ be in meters, but at zoom levels 3 and lower, at larger screen sizes
    // // this calculation can quietly return values in km, so caution should be used if required at zoom 3 or lower.
    // const widthKm = Math.round(distWidthM / 1000) // m -> km
    // const heightKm = Math.round(distHeightM / 1000) // m -> km
    // scaleWidth = widthKm.toLocaleString(navigator.language, { minimumFractionDigits: 0 })
    // scaleHeight = heightKm.toLocaleString(navigator.language, { minimumFractionDigits: 0 })
  }

  fitBounds (bounds = INITIAL_BOUNDS) {
    const { mark2DViewModified } = this.props
    this.map.fitBounds(bounds)
    mark2DViewModified(false)
    // Reset this flag again after some time, as Leaflet will call view updated event at the end of animation.
    setTimeout(() => mark2DViewModified(false), 400)
    log('ResetMapClicked')
  }

  render () {
    const { mode, earthquakes, volcanoes, layers, crossSectionPoints, mapStatus, setCrossSectionPoint, pins } = this.props
    const { selectedEarthquake, selectedVolcano } = this.state
    const baseLayer = this.baseLayer
    let url = baseLayer.url
    if (baseLayer.url.indexOf('{c}') > -1) {
      // If screen scaled display has higher than 1 pixel ratio, request scaled tiles, otherwise just request regular tiles
      // Since devicePixelRatio can change between displays and we can't guarantee how many versions of the base tiles there are available,
      // limit the request scaling to 2x if the user has any scale of higher dpi display.
      // There's no visible issue in requesting 2x on a regular dpi display aside from bandwidth
      url = window.devicePixelRatio && window.devicePixelRatio !== 1 ? baseLayer.url.replace('{c}', '@2x') : baseLayer.url.replace('{c}', '')
    }

    const allowDragging = config.allowDrag
    const allowFreeMouseZoom = config.sizeToFitBounds ? true : 'center'
    const bounds = config.sizeToFitBounds ? INITIAL_BOUNDS : undefined
    const center = config.sizeToFitBounds ? undefined : INITIAL_CENTER
    const zoom = this.map ? this.mapZoom : config.centeredInitialZoom

    const showZoomControls = config.zoomMin !== config.zoomMax

    return (
      <div className={`seismic-eruptions-map mode-${mode}`}>
        <Map ref='map' className='map'
          onViewportChanged={this.handleMapViewportChanged}
          bounds={bounds}
          minZoom={config.zoomMin > -1 ? config.zoomMin : 2}
          dragging={allowDragging}
          zoom={zoom}
          center={center}
          doubleClickZoom={allowFreeMouseZoom}
          scrollWheelZoom={allowFreeMouseZoom}
          onzoomend={this.handleZoom}
          zoomControl={showZoomControls}
        >
          {/* #key attribute is very important here. #subdomains is not a dynamic property, so we can't reuse the same */}
          {/* component instance when we switch between maps with subdomains and without. */}
          <TileLayer key={baseLayer.type} url={url} subdomains={baseLayer.subdomains} attribution={baseLayer.attribution} />
          {layers.get('plateBoundaries') && <PlateBoundariesLayer mapRegion={mapStatus.get('region')} mapZoom={mapStatus.get('zoom')} />}
          {layers.get('continentOceanNames') && <LabelsLayer mapRegion={mapStatus.get('region')} labels={continentOceanNames} />}
          {layers.get('plateNames') && <LabelsLayer mapRegion={mapStatus.get('region')} labels={plateNames} />}
          {layers.get('plateArrows') && <PlateArrowsLayer mapRegion={mapStatus.get('region')} />}
          {layers.get('plateMovement') && <PlateMovementLayer />}
          {pins && <PinsLayer mapRegion={mapStatus.get('region')} pins={pins} onPinUpdated={this.handlePinUpdated} />}
          {mode !== '3d' &&
            /* Performance optimization. Update of this component is expensive. Remove it when the map is invisible. */
            <SpritesLayer earthquakes={earthquakes} volcanoes={volcanoes}
              onEarthquakeClick={this.handleEarthquakeClick} onVolcanoClick={this.handleVolcanoClick} />
          }
          {mode === '2d' && selectedEarthquake &&
            <EarthquakePopup earthquake={selectedEarthquake} onPopupClose={this.handleEarthquakePopupClose} />
          }
          {mode === '2d' && selectedVolcano &&
            <VolcanoPopup volcano={selectedVolcano} onPopupClose={this.handleVolcanoPopupClose} />
          }
          {mode === 'cross-section' &&
            <CrossSectionDrawLayer crossSectionPoints={crossSectionPoints} setCrossSectionPoint={setCrossSectionPoint} />
          }
          {!config.showUserInterface && showZoomControls &&
            <ScaleControl position={'topleft'} />
          }
        </Map>
        {!config.showUserInterface && showZoomControls &&

{`Zoom level: ${zoom}`}

        }

    )
  }
}

function createSVGOverlayLayer (map) {
  map.addLayer(new Circle([0, 0], 0, { opacity: 0, fillOpacity: 0 }))
}
<|endoftext|>


<commit_before>'use strict';

var Command   = require('../models/command');
var Watcher   = require('../models/watcher');
var Builder   = require('../models/builder');

var fs = require('fs');
var path = require('path');

module.exports = Command.extend({
  name: 'test',
  aliases: ['test', 't'],
  description: 'Runs your apps test suite.',

  availableOptions: [
    { name: 'environment', type: String, default: 'test' },
    { name: 'config-file', type: String,  default: './testem.json' },
    { name: 'server',      type: Boolean, default: false},
    { name: 'port',        type: Number,  default: 7357, description: 'The port to use when running with --server.'},
    { name: 'filter',      type: String,  description: 'A regex to filter tests ran'},
    { name: 'watcher',     type: String,  default: 'events' },
  ],

  init: function() {
    this.assign    = require('lodash-node/modern/objects/assign');
    this.quickTemp = require('quick-temp');

    this.Builder = this.Builder || Builder;
    this.Watcher = this.Watcher || Watcher;

    if (!this.testing) {
      process.env.EMBER_CLI_TEST_COMMAND = true;
    }
  },

  tmp: function() {
    return this.quickTemp.makeOrRemake(this, '-testsDist');
  },

  rmTmp: function() {
    this.quickTemp.remove(this, '-testsDist');
    this.quickTemp.remove(this, '-customConfigFile');
  },

  _generateCustomConfigFile: function(options) {
    if (!options.filter) { return options.configFile; }

    var tmpPath = this.quickTemp.makeOrRemake(this, '-customConfigFile');
    var customPath = path.join(tmpPath, 'testem.json');
    var originalContents = JSON.parse(fs.readFileSync(options.configFile, { encoding: 'utf8' }));

    originalContents['test_page'] = originalContents['test_page'] + '?filter=' + options.filter;

    fs.writeFileSync(customPath, JSON.stringify(originalContents));

    return customPath;
  },

  run: function(commandOptions) {
    var outputPath  = this.tmp();
    var testOptions = this.assign({}, commandOptions, {
      outputPath: outputPath,
      project: this.project,
      configFile: this._generateCustomConfigFile(commandOptions)
    });

    var options = {
      ui: this.ui,
      analytics: this.analytics,
      project: this.project
    };

    if (commandOptions.server) {
      options.builder = new this.Builder(testOptions);

      var TestServerTask = this.tasks.TestServer;
      var testServer     = new TestServerTask(options);

      testOptions.watcher = new this.Watcher(this.assign(options, {
        verbose: false,
        options: commandOptions
      }));

      return testServer.run(testOptions)
        .finally(this.rmTmp.bind(this));
    } else {
      var TestTask  = this.tasks.Test;
      var BuildTask = this.tasks.Build;

      var test  = new TestTask(options);
      var build = new BuildTask(options);

      return build.run({
          environment: commandOptions.environment,
          outputPath: outputPath
        })
        .then(function() {
          return test.run(testOptions);
        })
        .finally(this.rmTmp.bind(this));
    }
  }
});
<commit_msg>short-circuit failure in build task when calling test task<commit_after>'use strict';

var Command   = require('../models/command');
var Watcher   = require('../models/watcher');
var Builder   = require('../models/builder');

var fs = require('fs');
var path = require('path');

module.exports = Command.extend({
  name: 'test',
  aliases: ['test', 't'],
  description: 'Runs your apps test suite.',

  availableOptions: [
    { name: 'environment', type: String, default: 'test' },
    { name: 'config-file', type: String,  default: './testem.json' },
    { name: 'server',      type: Boolean, default: false},
    { name: 'port',        type: Number,  default: 7357, description: 'The port to use when running with --server.'},
    { name: 'filter',      type: String,  description: 'A regex to filter tests ran'},
    { name: 'watcher',     type: String,  default: 'events' },
  ],

  init: function() {
    this.assign    = require('lodash-node/modern/objects/assign');
    this.quickTemp = require('quick-temp');

    this.Builder = this.Builder || Builder;
    this.Watcher = this.Watcher || Watcher;

    if (!this.testing) {
      process.env.EMBER_CLI_TEST_COMMAND = true;
    }
  },

  tmp: function() {
    return this.quickTemp.makeOrRemake(this, '-testsDist');
  },

  rmTmp: function() {
    this.quickTemp.remove(this, '-testsDist');
    this.quickTemp.remove(this, '-customConfigFile');
  },

  _generateCustomConfigFile: function(options) {
    if (!options.filter) { return options.configFile; }

    var tmpPath = this.quickTemp.makeOrRemake(this, '-customConfigFile');
    var customPath = path.join(tmpPath, 'testem.json');
    var originalContents = JSON.parse(fs.readFileSync(options.configFile, { encoding: 'utf8' }));

    originalContents['test_page'] = originalContents['test_page'] + '?filter=' + options.filter;

    fs.writeFileSync(customPath, JSON.stringify(originalContents));

    return customPath;
  },

  run: function(commandOptions) {
    var outputPath  = this.tmp();
    var testOptions = this.assign({}, commandOptions, {
      outputPath: outputPath,
      project: this.project,
      configFile: this._generateCustomConfigFile(commandOptions)
    });

    var options = {
      ui: this.ui,
      analytics: this.analytics,
      project: this.project
    };

    if (commandOptions.server) {
      options.builder = new this.Builder(testOptions);

      var TestServerTask = this.tasks.TestServer;
      var testServer     = new TestServerTask(options);

      testOptions.watcher = new this.Watcher(this.assign(options, {
        verbose: false,
        options: commandOptions
      }));

      return testServer.run(testOptions)
        .finally(this.rmTmp.bind(this));
    } else {
      var TestTask  = this.tasks.Test;
      var BuildTask = this.tasks.Build;

      var test  = new TestTask(options);
      var build = new BuildTask(options);

      return build.run({
          environment: commandOptions.environment,
          outputPath: outputPath
        })
        .then(function(exitCode) {
          if (exitCode) {
            return exitCode;
          }
          return test.run(testOptions);
        })
        .finally(this.rmTmp.bind(this));
    }
  }
});
<|endoftext|>


<commit_before>define([
	'knockout',
	'd3',
	'd3-tip',
	'lodash',
	'd3-selection',
	'services/MomentAPI',
	'services/AuthAPI',
	'appConfig',
	'extensions/d3-labeler',
], function (
	ko,
	d3,
	d3tip,
	_,
	d3Selection,
	momentApi,
	authApi,
	config
) {
	"use strict";

	function canViewProfileDates() {
		return config.viewProfileDates && (!config.userAuthenticationEnabled || (config.userAuthenticationEnabled && authApi.isPermittedViewProfileDates()));
	}

	var margin = {
		get top() {
			return canViewProfileDates() ? 30 : 10;
		},
		right: 20,
		bottom: 30,
		left: 20
	};

	var vizHeight = 200;
	var width = 900 - margin.left - margin.right;

	var xScale = d3.scaleLinear();
	var x2Scale = d3.scaleTime();
	var yScale = d3.scaleBand();

	var tipText = d => {
		return d.conceptName;
	};

	var htmlTipText = d => {
		var tipText = '
Event: ' + d.conceptName + '

Start Day: ' + d.startDay + '
';
		if (authApi.isPermittedViewProfileDates() && d.startDate != null) {
			tipText += '
Start Date: ' + momentApi.formatDate(new Date(d.startDate)) + '
'
		}
		return tipText;
	};
	var pointClass = d => d.domain;
	var radius = d => 2;

	var highlightFunc = () => {};
	var zoomDimension = null;
	var xfObservable = null;

	var focusTip = d3tip()
		.attr('class', 'd3-tip')
		.offset(function (d) {
			return [-10, 0];
		})
		.html(function (d) {
			return htmlTipText(d);
		});

	ko.bindingHandlers.profileChart = {
		init: function (element, valueAccessor, allBindingsAccessor) {
			valueAccessor().highlightRecs.subscribe(xfObservable => {
				highlightFunc(xfObservable);
			});
		},
		update: function (element, valueAccessor, allBindingsAccessor) {
			var va = valueAccessor();

			vizHeight = 250 - margin.top - margin.bottom;
			va.aspectRatio((vizHeight + margin.top + margin.bottom) / width);

			if (va.xfObservable() === undefined)
				return;

			if (width < 100)
				return;

			var svg = categoryScatterPlot(element,
				va.xfObservable,
				ko.utils.unwrapObservable(va.verticalLines || []),
				ko.utils.unwrapObservable(va.shadedRegions || []),
				va.xfDimensions
			);

			// maintain highlights
			valueAccessor().highlightRecs.valueHasMutated();
		}
	};

	function categoryScatterPlot(element, xfo, verticalLines, shadedRegions, xfd) {

		function rectangle(datum) {
			var minBoxPix = 5;
			var g = d3.select(this);
			g.selectAll('rect.point.' + pointClass(datum))
				.data([datum])
				.enter()
				.append('rect')
				.classed('point', true)
				.classed(pointClass(datum), true);
			g.selectAll('rect.point.' + pointClass(datum))
				.attr('height', minBoxPix)
				.attr('width', function (d) {
					return calculateWidth(d);
				})
				.attr('x', function (d) {
					return -minBoxPix / 2;
				})
				.attr('y', -minBoxPix / 2)
		}

		function brushEnded() {
			var s = d3.event.selection;
			if (s === null) {
				xfd[0].filter(null);
			} else {
				xfd[0].filterFunction(function (d) {
					return xScale.invert(s[0]) <= d.startDay && d.startDay <= xScale.invert(s[1]) ||
						xScale.invert(s[0]) <= d.endDay && d.endDay <= xScale.invert(s[1]);
				}); // start day
			}
			xfObservable.valueHasMutated();
		}

		var points = xfo().allFiltered();

		// prevent filtering to no data and error in chart
		if (points.length == 0) {
			xfd[0].filter(null);
			points = xfo().allFiltered();
		}

		var fullDomain = d3.extent([].concat.apply([], points.map(d => [d.startDay, d.endDay])));
		xScale.domain(fullDomain).range([0, width]);

		var fullDateDomain = d3.extent([].concat.apply([], points.map(d => [d.startDate, d.endDate])));
		x2Scale.domain(fullDateDomain).range([0, width]);

		xfObservable = xfo;
		var categories = _.chain(points).map(d => d.domain).uniq().value();
		yScale.domain(categories.sort())
			.range([0, vizHeight]);

		$(element).empty();

		var svg = d3.select(element).append("svg")
			.attr("preserveAspectRatio", "xMinYMin meet")
			.attr("viewBox", `0 0 ${width + margin.left + margin.right} ${vizHeight + margin.top + margin.bottom}`)

		var profilePlot = svg.append("g")
			.attr("class", "profilePlot")
			.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

		var xAxis = d3.axisBottom().scale(xScale);
		var x2Axis = d3.axisTop().scale(x2Scale);
		svg.call(focusTip);

		var letterSpacingScale = d3.scaleLog()
			.domain([.8, 20])
			.range([-.05, 2]);

		var fontSizeScale = d3.scalePow(1 / 2)
			.domain([10, vizHeight])
			.range([15, 75]);

		var categoryContainer = profilePlot.append("g");

		categoryContainer.selectAll('rect.category')
			.data(categories)
			.enter()
			.append('rect')
			.attr('class', 'category')
			.attr('width', width)
			.attr('height', yScale.bandwidth())
			.attr('y', d => yScale(d));

		profilePlot.selectAll('text.category')
			.data(categories)
			.enter()
			.append('text')
			.attr('class', 'category')
			.attr('font-size', fontSizeScale(yScale.bandwidth()))
			.attr('y', d => yScale(d) + yScale.bandwidth() / 2 + fontSizeScale(yScale.bandwidth()) / 2)
			.attr('x', width / 2)
			.attr('text-anchor', 'middle')
			.style('letter-spacing', 0)
			.text(d => d + 's')
			.style('letter-spacing', function (d) {
				var ratio = width / this.getBBox().width;
				var ls = letterSpacingScale(ratio);
				return ls + 'em';
			});

		var regions = profilePlot.selectAll('rect.shaded-region')
			.data(shadedRegions)
			.enter()
			.append('rect')
			.attr('class', function (sr) {
				return sr.className;
			})
			.classed('shaded-region', true);

		profilePlot.selectAll('rect.shaded-region')
			.attr('x', d => xScale(d.x1))
			.attr('y', yScale.range()[0])
			.attr('width', d => xScale(d.x2) - xScale(d.x1))
			.attr('height', yScale.range()[1])

		var vLines = profilePlot.selectAll('line.vertical')
			.data(verticalLines);
		vLines.exit().remove();
		vLines.enter()
			.append('line')
			.attr('class', 'vertical');

		profilePlot.selectAll('line.vertical')
			.attr('x1', d => xScale(d.x))
			.attr('x2', d => xScale(d.x))
			.attr('y1', yScale.range()[0])
			.attr('y2', yScale.range()[1])
			.style('stroke', d => d.color);

		var brush = d3.brushX().extent([
				[0, 0],
				[width, yScale.bandwidth() * categories.length]
			])
			.on("end", brushEnded);

		categoryContainer.append("g")
			.attr("class", "brush")
			.call(brush);

		var pointGs = profilePlot.selectAll("g.point")
			.data(points)
			.enter()
			.append("g")
			.classed('point', true);

		profilePlot.selectAll("g.point")
			.attr("transform", function (d, i) {
				return "translate(" + xScale(d.startDay) + "," + (yScale(d.domain) + jitter(i).y) + ")";
			})
			.attr('class', function (d) {
				return rectangle(d);
			})
			.classed('point', true)
			.on('mouseover', (d) => {
				d3Selection.event = d3.event; // otherwise, d3Selection.event is null
				return focusTip.show(d);
			})
			.on('mouseout', focusTip.hide)
			.each(rectangle)

		if (points.length <= 50) {
			// labeler usage from https://github.com/tinker10/D3-Labeler demo
			var label_array = points.map((d, i) => {
				d.x = xScale(d.startDay) + jitter(i).x;
				d.y = yScale(d.domain) + jitter(i).y;
				d.r = 8;
				return {
					x: d.x,
					y: d.y,
					name: tipText(d),
					width: 0,
					height: 0,
					rec: d,
				};
			});
			var labels = profilePlot.selectAll('.labels')
				.data(label_array)
				.enter()
				.append('text')
				.attr('class', 'label')
				.attr('text-anchor', 'start')
				.text(d => d.name)
				.attr('x', d => d.x)
				.attr('y', d => d.y)
				.attr('fill', 'black');
			var index = 0;
			labels.each(function () {
				label_array[index].width = this.getBBox().width;
				label_array[index].height = this.getBBox().height;
				index += 1;
			});
			var links = profilePlot.selectAll('.link')
				.data(label_array)
				.enter()
				.append('line')
				.attr('class', 'link')
				.attr('x1', d => d.x)
				.attr('y1', d => d.y)
				.attr('x2', d => d.x)
				.attr('y2', d => d.y)
			var sim_ann = d3.labeler()
				.label(label_array)
				.anchor(points)
				.width(width)
				.height(vizHeight)
				.start();

			labels.attr('x', d => d.x)
				.attr('y', d => d.y);
			links.attr('x2', d => d.x)
				.attr('y2', d => d.y);
		}

		function addAxis(axis, top) {
			profilePlot.append("g")
				.attr("class", "x axis")
				.attr("transform", "translate(0," + top + ")")
				.call(axis);
		}

		addAxis(xAxis, vizHeight + 2);

		if (canViewProfileDates()) {
			addAxis(x2Axis, 0);
		}

		highlightFunc = function () {
			pointGs.selectAll('rect')
				.style('fill', function (d) {
					return d.highlight;
				})
				.style('stroke', function (d) {
					return d.stroke;
				});
		};
		return svg;
	}

	function calculateWidth(d) {
		var minBoxPix = 5;
		var startDayX = xScale(d.startDay);
		var endDayX = xScale(d.endDay);
		var daysX = Math.abs(startDayX - endDayX);
		return Math.max(minBoxPix, daysX);
	}

	var jitterOffsets = []; // keep them stable as points move around
	var jitterYScale = d3.scaleLinear().domain([-.5, .5]);

	function jitter(i, maxX = 6, maxY = 12) {
		jitterYScale.range([yScale.bandwidth() * .1, yScale.bandwidth() * .9]);
		jitterOffsets[i] = jitterOffsets[i] || [Math.random() - .5, Math.random() - .5];
		return {
			x: jitterOffsets[i][0] * maxX,
			y: jitterYScale(jitterOffsets[i][1])
		};
	}

	function makeFilter(ext) {
		var filter = function ([start, end] = []) {
			return (
				(start >= ext[0] && start <= ext[1]) ||
				(end >= ext[0] && end <= ext[1]) ||
				(start <= ext[0] && end >= ext[1])
			);
		};
		filter.ext = () => ext;
		return filter;
	}
});<commit_msg>Fixes tooltips in Profiles page (#1466)<commit_after>define([
	'knockout',
	'd3',
	'd3-tip',
	'lodash',
	'd3-selection',
	'services/MomentAPI',
	'services/AuthAPI',
	'appConfig',
	'extensions/d3-labeler',
], function (
	ko,
	d3,
	d3tip,
	_,
	d3Selection,
	momentApi,
	authApi,
	config
) {
	"use strict";

	function canViewProfileDates() {
		return config.viewProfileDates && (!config.userAuthenticationEnabled || (config.userAuthenticationEnabled && authApi.isPermittedViewProfileDates()));
	}

	var margin = {
		get top() {
			return canViewProfileDates() ? 30 : 10;
		},
		right: 20,
		bottom: 30,
		left: 20
	};

	var vizHeight = 200;
	var width = 900 - margin.left - margin.right;

	var xScale = d3.scaleLinear();
	var x2Scale = d3.scaleTime();
	var yScale = d3.scaleBand();

	var tipText = d => {
		return d.conceptName;
	};

	var htmlTipText = d => {
		var tipText = '
Event: ' + d.conceptName + '

Start Day: ' + d.startDay + '
';
		if (authApi.isPermittedViewProfileDates() && d.startDate != null) {
			tipText += '
Start Date: ' + momentApi.formatDate(new Date(d.startDate)) + '
'
		}
		return tipText;
	};
	var pointClass = d => d.domain;
	var radius = d => 2;

	var highlightFunc = () => {};
	var zoomDimension = null;
	var xfObservable = null;

	var focusTip = d3tip()
		.attr('class', 'd3-tip')
		.offset(function (d) {
			return [-10, 0];
		})
		.html(function (d) {
			return htmlTipText(d);
		});

	ko.bindingHandlers.profileChart = {
		init: function (element, valueAccessor, allBindingsAccessor) {
			valueAccessor().highlightRecs.subscribe(xfObservable => {
				highlightFunc(xfObservable);
			});
		},
		update: function (element, valueAccessor, allBindingsAccessor) {
			var va = valueAccessor();

			vizHeight = 250 - margin.top - margin.bottom;
			va.aspectRatio((vizHeight + margin.top + margin.bottom) / width);

			if (va.xfObservable() === undefined)
				return;

			if (width < 100)
				return;

			var svg = categoryScatterPlot(element,
				va.xfObservable,
				ko.utils.unwrapObservable(va.verticalLines || []),
				ko.utils.unwrapObservable(va.shadedRegions || []),
				va.xfDimensions
			);

			// maintain highlights
			valueAccessor().highlightRecs.valueHasMutated();
		}
	};

	function categoryScatterPlot(element, xfo, verticalLines, shadedRegions, xfd) {

		function rectangle(datum) {
			var minBoxPix = 5;
			var g = d3.select(this);
			g.selectAll('rect.point.' + pointClass(datum))
				.data([datum])
				.enter()
				.append('rect')
				.classed('point', true)
				.classed(pointClass(datum), true);
			g.selectAll('rect.point.' + pointClass(datum))
				.attr('height', minBoxPix)
				.attr('width', function (d) {
					return calculateWidth(d);
				})
				.attr('x', function (d) {
					return -minBoxPix / 2;
				})
				.attr('y', -minBoxPix / 2)
		}

		function brushEnded() {
			var s = d3.event.selection;
			if (s === null) {
				xfd[0].filter(null);
			} else {
				xfd[0].filterFunction(function (d) {
					return xScale.invert(s[0]) <= d.startDay && d.startDay <= xScale.invert(s[1]) ||
						xScale.invert(s[0]) <= d.endDay && d.endDay <= xScale.invert(s[1]);
				}); // start day
			}
			xfObservable.valueHasMutated();
		}

		var points = xfo().allFiltered();

		// prevent filtering to no data and error in chart
		if (points.length == 0) {
			xfd[0].filter(null);
			points = xfo().allFiltered();
		}

		var fullDomain = d3.extent([].concat.apply([], points.map(d => [d.startDay, d.endDay])));
		xScale.domain(fullDomain).range([0, width]);

		var fullDateDomain = d3.extent([].concat.apply([], points.map(d => [d.startDate, d.endDate])));
		x2Scale.domain(fullDateDomain).range([0, width]);

		xfObservable = xfo;
		var categories = _.chain(points).map(d => d.domain).uniq().value();
		yScale.domain(categories.sort())
			.range([0, vizHeight]);

		$(element).empty();

		var svg = d3.select(element).append("svg")
			.attr("preserveAspectRatio", "xMinYMin meet")
			.attr("viewBox", `0 0 ${width + margin.left + margin.right} ${vizHeight + margin.top + margin.bottom}`)

		var profilePlot = svg.append("g")
			.attr("class", "profilePlot")
			.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

		var xAxis = d3.axisBottom().scale(xScale);
		var x2Axis = d3.axisTop().scale(x2Scale);
		svg.call(focusTip);

		var letterSpacingScale = d3.scaleLog()
			.domain([.8, 20])
			.range([-.05, 2]);

		var fontSizeScale = d3.scalePow(1 / 2)
			.domain([10, vizHeight])
			.range([15, 75]);

		var categoryContainer = profilePlot.append("g");

		categoryContainer.selectAll('rect.category')
			.data(categories)
			.enter()
			.append('rect')
			.attr('class', 'category')
			.attr('width', width)
			.attr('height', yScale.bandwidth())
			.attr('y', d => yScale(d));

		profilePlot.selectAll('text.category')
			.data(categories)
			.enter()
			.append('text')
			.attr('class', 'category')
			.attr('font-size', fontSizeScale(yScale.bandwidth()))
			.attr('y', d => yScale(d) + yScale.bandwidth() / 2 + fontSizeScale(yScale.bandwidth()) / 2)
			.attr('x', width / 2)
			.attr('text-anchor', 'middle')
			.style('letter-spacing', 0)
			.text(d => d + 's')
			.style('letter-spacing', function (d) {
				var ratio = width / this.getBBox().width;
				var ls = letterSpacingScale(ratio);
				return ls + 'em';
			});

		var regions = profilePlot.selectAll('rect.shaded-region')
			.data(shadedRegions)
			.enter()
			.append('rect')
			.attr('class', function (sr) {
				return sr.className;
			})
			.classed('shaded-region', true);

		profilePlot.selectAll('rect.shaded-region')
			.attr('x', d => xScale(d.x1))
			.attr('y', yScale.range()[0])
			.attr('width', d => xScale(d.x2) - xScale(d.x1))
			.attr('height', yScale.range()[1])

		var vLines = profilePlot.selectAll('line.vertical')
			.data(verticalLines);
		vLines.exit().remove();
		vLines.enter()
			.append('line')
			.attr('class', 'vertical');

		profilePlot.selectAll('line.vertical')
			.attr('x1', d => xScale(d.x))
			.attr('x2', d => xScale(d.x))
			.attr('y1', yScale.range()[0])
			.attr('y2', yScale.range()[1])
			.style('stroke', d => d.color);

		var brush = d3.brushX().extent([
				[0, 0],
				[width, yScale.bandwidth() * categories.length]
			])
			.on("end", brushEnded);

		categoryContainer.append("g")
			.attr("class", "brush")
			.call(brush);

		var pointGs = profilePlot.selectAll("g.point")
			.data(points)
			.enter()
			.append("g")
			.classed('point', true);

		profilePlot.selectAll("g.point")
			.attr("transform", function (d, i) {
				return "translate(" + xScale(d.startDay) + "," + (yScale(d.domain) + jitter(i).y) + ")";
			})
			.attr('class', function (d) {
				return rectangle(d);
			})
			.classed('point', true)
			.on('mouseover', focusTip.show)
			.on('mouseout', focusTip.hide)
			.each(rectangle)

		if (points.length <= 50) {
			// labeler usage from https://github.com/tinker10/D3-Labeler demo
			var label_array = points.map((d, i) => {
				d.x = xScale(d.startDay) + jitter(i).x;
				d.y = yScale(d.domain) + jitter(i).y;
				d.r = 8;
				return {
					x: d.x,
					y: d.y,
					name: tipText(d),
					width: 0,
					height: 0,
					rec: d,
				};
			});
			var labels = profilePlot.selectAll('.labels')
				.data(label_array)
				.enter()
				.append('text')
				.attr('class', 'label')
				.attr('text-anchor', 'start')
				.text(d => d.name)
				.attr('x', d => d.x)
				.attr('y', d => d.y)
				.attr('fill', 'black');
			var index = 0;
			labels.each(function () {
				label_array[index].width = this.getBBox().width;
				label_array[index].height = this.getBBox().height;
				index += 1;
			});
			var links = profilePlot.selectAll('.link')
				.data(label_array)
				.enter()
				.append('line')
				.attr('class', 'link')
				.attr('x1', d => d.x)
				.attr('y1', d => d.y)
				.attr('x2', d => d.x)
				.attr('y2', d => d.y)
			var sim_ann = d3.labeler()
				.label(label_array)
				.anchor(points)
				.width(width)
				.height(vizHeight)
				.start();

			labels.attr('x', d => d.x)
				.attr('y', d => d.y);
			links.attr('x2', d => d.x)
				.attr('y2', d => d.y);
		}

		function addAxis(axis, top) {
			profilePlot.append("g")
				.attr("class", "x axis")
				.attr("transform", "translate(0," + top + ")")
				.call(axis);
		}

		addAxis(xAxis, vizHeight + 2);

		if (canViewProfileDates()) {
			addAxis(x2Axis, 0);
		}

		highlightFunc = function () {
			pointGs.selectAll('rect')
				.style('fill', function (d) {
					return d.highlight;
				})
				.style('stroke', function (d) {
					return d.stroke;
				});
		};
		return svg;
	}

	function calculateWidth(d) {
		var minBoxPix = 5;
		var startDayX = xScale(d.startDay);
		var endDayX = xScale(d.endDay);
		var daysX = Math.abs(startDayX - endDayX);
		return Math.max(minBoxPix, daysX);
	}

	var jitterOffsets = []; // keep them stable as points move around
	var jitterYScale = d3.scaleLinear().domain([-.5, .5]);

	function jitter(i, maxX = 6, maxY = 12) {
		jitterYScale.range([yScale.bandwidth() * .1, yScale.bandwidth() * .9]);
		jitterOffsets[i] = jitterOffsets[i] || [Math.random() - .5, Math.random() - .5];
		return {
			x: jitterOffsets[i][0] * maxX,
			y: jitterYScale(jitterOffsets[i][1])
		};
	}

	function makeFilter(ext) {
		var filter = function ([start, end] = []) {
			return (
				(start >= ext[0] && start <= ext[1]) ||
				(end >= ext[0] && end <= ext[1]) ||
				(start <= ext[0] && end >= ext[1])
			);
		};
		filter.ext = () => ext;
		return filter;
	}
});<|endoftext|>


<commit_before>import Component from 'flarum/component';
import classList from 'flarum/utils/class-list';
import LoadingIndicator from 'flarum/components/loading-indicator';

export default class StreamItem extends Component {
  /**

   */
  constructor(props) {
    super(props);

    this.element = m.prop();
  }

  /**

   */
  view() {
    var component = this;
    var item = this.props.item;

    var gap = !item.post;
    var direction = item.direction;
    var loading = item.loading;
    var count = item.end - item.start + 1;
    var classes = { item: true, gap, loading, direction };

    var attributes = {
      className: classList(classes),
      config: this.element,
      'data-start': item.start,
      'data-end': item.end
    };
    if (!gap) {
      attributes['data-time'] = item.post.time();
      attributes['data-number'] = item.post.number();
    } else {
      attributes['config'] = (element) => {
        this.element(element);
        element.instance = this;
      };
      attributes['onclick'] = this.load.bind(this);
      attributes['onmouseenter'] = function(e) {
        if (!item.loading) {
          var $this = $(this);
          var up = e.clientY > $this.offset().top - $(document).scrollTop() + $this.outerHeight(true) / 2;
          $this.removeClass('up down').addClass(item.direction = up ? 'up' : 'down');
        }
        m.redraw.strategy('none');
      };
    }

    var content;
    if (gap) {
      content = m('span', loading ? LoadingIndicator.component() : count+' more post'+(count !== 1 ? 's' : ''));
    } else {
      var PostComponent = app.postComponentRegistry[item.post.contentType()];
      if (PostComponent) {
        content = PostComponent.component({post: item.post, ondelete: this.props.ondelete});
      }
    }

    return m('div', attributes, content);
  }

  /**

   */
  load() {
    var item = this.props.item;

    // If this item is not a gap, or if we're already loading its posts,
    // then we don't need to do anything.
    if (item.post || item.loading) {
      return false;
    }

    // If new posts are being loaded in an upwards direction, then when
    // they are rendered, the rest of the posts will be pushed down the
    // page. If loaded in a downwards direction from the end of a
    // discussion, the terminal gap will disappear and the page will
    // scroll up a bit before the new posts are rendered. In order to
    // maintain the current scroll position relative to the content
    // before/after the gap, we need to find item directly after the gap
    // and use it as an anchor.
    var siblingFunc = item.direction === 'up' ? 'nextAll' : 'prevAll';
    var anchor = this.$()[siblingFunc]('.item:first');

    // Tell the controller that we want to load the range of posts that this
    // gap represents. We also specify which direction we want to load the
    // posts from.
    this.props.loadRange(item.start, item.end, item.direction === 'up').then(function() {
      // Immediately after the posts have been loaded (but before they
      // have been rendered,) we want to grab the distance from the top of
      // the viewport to the top of the anchor element.
      if (anchor.length) {
        var scrollOffset = anchor.offset().top - $(document).scrollTop();
      }

      m.redraw(true);

      // After they have been rendered, we scroll back to a position
      // so that the distance from the top of the viewport to the top
      // of the anchor element is the same as before. If there is no
      // anchor (i.e. this gap is terminal,) then we'll scroll to the
      // bottom of the document.
      $('body').scrollTop(anchor.length ? anchor.offset().top - scrollOffset : $('body').height());
    });

    m.redraw();
  }
}
<commit_msg>Prevent moment deprecation warning<commit_after>import Component from 'flarum/component';
import classList from 'flarum/utils/class-list';
import LoadingIndicator from 'flarum/components/loading-indicator';

export default class StreamItem extends Component {
  /**

   */
  constructor(props) {
    super(props);

    this.element = m.prop();
  }

  /**

   */
  view() {
    var component = this;
    var item = this.props.item;

    var gap = !item.post;
    var direction = item.direction;
    var loading = item.loading;
    var count = item.end - item.start + 1;
    var classes = { item: true, gap, loading, direction };

    var attributes = {
      className: classList(classes),
      config: this.element,
      'data-start': item.start,
      'data-end': item.end
    };
    if (!gap) {
      attributes['data-time'] = item.post.time().toISOString();
      attributes['data-number'] = item.post.number();
    } else {
      attributes['config'] = (element) => {
        this.element(element);
        element.instance = this;
      };
      attributes['onclick'] = this.load.bind(this);
      attributes['onmouseenter'] = function(e) {
        if (!item.loading) {
          var $this = $(this);
          var up = e.clientY > $this.offset().top - $(document).scrollTop() + $this.outerHeight(true) / 2;
          $this.removeClass('up down').addClass(item.direction = up ? 'up' : 'down');
        }
        m.redraw.strategy('none');
      };
    }

    var content;
    if (gap) {
      content = m('span', loading ? LoadingIndicator.component() : count+' more post'+(count !== 1 ? 's' : ''));
    } else {
      var PostComponent = app.postComponentRegistry[item.post.contentType()];
      if (PostComponent) {
        content = PostComponent.component({post: item.post, ondelete: this.props.ondelete});
      }
    }

    return m('div', attributes, content);
  }

  /**

   */
  load() {
    var item = this.props.item;

    // If this item is not a gap, or if we're already loading its posts,
    // then we don't need to do anything.
    if (item.post || item.loading) {
      return false;
    }

    // If new posts are being loaded in an upwards direction, then when
    // they are rendered, the rest of the posts will be pushed down the
    // page. If loaded in a downwards direction from the end of a
    // discussion, the terminal gap will disappear and the page will
    // scroll up a bit before the new posts are rendered. In order to
    // maintain the current scroll position relative to the content
    // before/after the gap, we need to find item directly after the gap
    // and use it as an anchor.
    var siblingFunc = item.direction === 'up' ? 'nextAll' : 'prevAll';
    var anchor = this.$()[siblingFunc]('.item:first');

    // Tell the controller that we want to load the range of posts that this
    // gap represents. We also specify which direction we want to load the
    // posts from.
    this.props.loadRange(item.start, item.end, item.direction === 'up').then(function() {
      // Immediately after the posts have been loaded (but before they
      // have been rendered,) we want to grab the distance from the top of
      // the viewport to the top of the anchor element.
      if (anchor.length) {
        var scrollOffset = anchor.offset().top - $(document).scrollTop();
      }

      m.redraw(true);

      // After they have been rendered, we scroll back to a position
      // so that the distance from the top of the viewport to the top
      // of the anchor element is the same as before. If there is no
      // anchor (i.e. this gap is terminal,) then we'll scroll to the
      // bottom of the document.
      $('body').scrollTop(anchor.length ? anchor.offset().top - scrollOffset : $('body').height());
    });

    m.redraw();
  }
}
<|endoftext|>


<commit_before>var annoLink;

(function($){
	var inputs = {}, ed;

	annoLink = {
		parentSelector: 'span[data-xmlel="ext-link"]',
		keySensitivity: 100,
		textarea: function() { return edCanvas; },

		init : function() {
			inputs.dialog = $('#anno-popup-link');
			inputs.submit = $('#anno-link-submit');
			inputs.url = $('#anno-link-url-field');
			inputs.title = $('#anno-link-title-field');
			inputs.alt = $('#anno-link-alt-field');

			// Bind event handlers
			inputs.dialog.keydown( annoLink.keydown );
			inputs.dialog.keyup( annoLink.keyup );
			inputs.submit.click( function(e){
				annoLink.update();
				e.preventDefault();
			});

			$('#anno-link-cancel').click(annoLink.close);

			inputs.dialog.bind('wpdialogrefresh', annoLink.refresh);
			inputs.dialog.bind('wpdialogbeforeopen', annoLink.beforeOpen);
			inputs.dialog.bind('wpdialogclose', annoLink.onClose);
		},

		beforeOpen : function() {
			annoLink.range = null;

			if ( ! annoLink.isMCE() && document.selection ) {
				annoLink.textarea().focus();
				annoLink.range = document.selection.createRange();
			}
		},

		isMCE : function() {
			return tinyMCEPopup && ( ed = tinyMCEPopup.editor ) && ! ed.isHidden();
		},

		refresh : function() {
			annoLink.mceRefresh();

			// Focus the URL field and highlight its contents.
			//     If this is moved above the selection changes,
			//     IE will show a flashing cursor over the dialog.
			inputs.url.focus()[0].select();
		},

		mceRefresh : function() {
			var e;
			ed = tinyMCEPopup.editor;

			tinyMCEPopup.restoreSelection();

			// If link exists, select proper values.
			if ( e = ed.dom.getParent(ed.selection.getNode(), this.parentSelector) ) {

				inputs.url.val( ed.dom.getAttrib(e, 'xlink:href'));
				inputs.title.val( ed.dom.getAttrib(e, 'title') );
				// Update save prompt.
				inputs.submit.val( annoLinkL10n.update );

			// If there's no link, set the default values.
			} else {
				annoLink.setDefaultValues();
			}

			tinyMCEPopup.storeSelection();
		},

		close : function() {
			tinyMCEPopup.close();
		},

		onClose: function() {
			if ( ! annoLink.isMCE() ) {
				annoLink.textarea().focus();
				if ( annoLink.range ) {
					annoLink.range.moveToBookmark( annoLink.range.getBookmark() );
					annoLink.range.select();
				}
			}
		},

		getAttrs : function() {
			var tmp_attrs = {};
			tmp_attrs['xlink:href'] = inputs.url.val();
			tmp_attrs['title'] = inputs.title.val();
			tmp_attrs['ext-link-type'] = 'uri';
			return tmp_attrs;
		},

		update : function() {
			var ed = tinyMCEPopup.editor,
				attrs = annoLink.getAttrs(),
				e, b;

			tinyMCEPopup.restoreSelection();
			e = ed.dom.getParent(ed.selection.getNode(), 'span');

			// If the values are empty, unlink and return
			if ( ! attrs['xlink:href'] || attrs['xlink:href'] == 'http://' ) {
				if ( e ) {
					tinyMCEPopup.execCommand("mceBeginUndoLevel");
					b = ed.selection.getBookmark();
					ed.dom.remove(e, 1);
					ed.selection.moveToBookmark(b);
					tinyMCEPopup.execCommand("mceEndUndoLevel");
					annoLink.close();
				}
				return;
			}

			tinyMCEPopup.execCommand("mceBeginUndoLevel");
			// Leverage the logic of CreateLink
			if (e == null) {
				tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1});

				tinymce.each(ed.dom.select("a"), function(n) {
					if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') {
						var new_element = document.createElement('span');
					 	var old_innerHTML = n.innerHTML;

					 	attrs['class'] = 'ext-link';
					 	attrs['data-xmlel'] = 'ext-link';
					 	attrs['xmlns:xlink'] = 'http://www.w3.org/1999/xlink';
						n.parentNode.replaceChild(new_element, n);
						e = new_element;
						ed.dom.setAttribs(e, attrs);
						e.innerHTML = old_innerHTML;
					}
				});

				// Sometimes WebKit lets a user create a link where
				// they shouldn't be able to. In this case, CreateLink
				// injects "#mce_temp_url#" into their content. Fix it.
				if ( $(e).text() == '#mce_temp_url#' ) {
					ed.dom.remove(e);
					e = null;
				}

			}
			else {
				ed.dom.setAttribs(e, attrs);
			}

			// Don't move caret if selection was image
			if ( e && (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') ) {
				ed.focus();
				ed.selection.select(e);
				ed.selection.collapse(0);
				tinyMCEPopup.storeSelection();
			}

			tinyMCEPopup.execCommand("mceEndUndoLevel");
			annoLink.close();
		},

		setDefaultValues : function() {
			// Set URL and descriptions to defaults.
			// Leave the new tab setting as-is.
			inputs.url.val('http://');
			inputs.title.val('');
		},

		keyup: function( event ) {
			var key = $.ui.keyCode;

			switch( event.which ) {
				case key.ESCAPE:
					event.stopImmediatePropagation();
					if ( ! $(document).triggerHandler( 'wp_CloseOnEscape', [{ event: event, what: 'annolink', cb: annoLink.close }] ) )
						annoLink.close();

					return false;
					break;
				default:
					return;
			}
			event.preventDefault();
		},
	}
	$(document).ready( annoLink.init );
})(jQuery);
<commit_msg>Use appropriate parent selector<commit_after>var annoLink;

(function($){
	var inputs = {}, ed;

	annoLink = {
		parentSelector: 'span[data-xmlel="ext-link"]',
		keySensitivity: 100,
		textarea: function() { return edCanvas; },

		init : function() {
			inputs.dialog = $('#anno-popup-link');
			inputs.submit = $('#anno-link-submit');
			inputs.url = $('#anno-link-url-field');
			inputs.title = $('#anno-link-title-field');
			inputs.alt = $('#anno-link-alt-field');

			// Bind event handlers
			inputs.dialog.keydown( annoLink.keydown );
			inputs.dialog.keyup( annoLink.keyup );
			inputs.submit.click( function(e){
				annoLink.update();
				e.preventDefault();
			});

			$('#anno-link-cancel').click(annoLink.close);

			inputs.dialog.bind('wpdialogrefresh', annoLink.refresh);
			inputs.dialog.bind('wpdialogbeforeopen', annoLink.beforeOpen);
			inputs.dialog.bind('wpdialogclose', annoLink.onClose);
		},

		beforeOpen : function() {
			annoLink.range = null;

			if ( ! annoLink.isMCE() && document.selection ) {
				annoLink.textarea().focus();
				annoLink.range = document.selection.createRange();
			}
		},

		isMCE : function() {
			return tinyMCEPopup && ( ed = tinyMCEPopup.editor ) && ! ed.isHidden();
		},

		refresh : function() {
			annoLink.mceRefresh();

			// Focus the URL field and highlight its contents.
			//     If this is moved above the selection changes,
			//     IE will show a flashing cursor over the dialog.
			inputs.url.focus()[0].select();
		},

		mceRefresh : function() {
			var e;
			ed = tinyMCEPopup.editor;

			tinyMCEPopup.restoreSelection();

			// If link exists, select proper values.
			if ( e = ed.dom.getParent(ed.selection.getNode(), this.parentSelector) ) {

				inputs.url.val( ed.dom.getAttrib(e, 'xlink:href'));
				inputs.title.val( ed.dom.getAttrib(e, 'title') );
				// Update save prompt.
				inputs.submit.val( annoLinkL10n.update );

			// If there's no link, set the default values.
			} else {
				annoLink.setDefaultValues();
			}

			tinyMCEPopup.storeSelection();
		},

		close : function() {
			tinyMCEPopup.close();
		},

		onClose: function() {
			if ( ! annoLink.isMCE() ) {
				annoLink.textarea().focus();
				if ( annoLink.range ) {
					annoLink.range.moveToBookmark( annoLink.range.getBookmark() );
					annoLink.range.select();
				}
			}
		},

		getAttrs : function() {
			var tmp_attrs = {};
			tmp_attrs['xlink:href'] = inputs.url.val();
			tmp_attrs['title'] = inputs.title.val();
			tmp_attrs['ext-link-type'] = 'uri';
			return tmp_attrs;
		},

		update : function() {
			var ed = tinyMCEPopup.editor,
				attrs = annoLink.getAttrs(),
				e, b;

			tinyMCEPopup.restoreSelection();
			e = ed.dom.getParent(ed.selection.getNode(), this.parentSelector);

			// If the values are empty, unlink and return
			if ( ! attrs['xlink:href'] || attrs['xlink:href'] == 'http://' ) {
				if ( e ) {
					tinyMCEPopup.execCommand("mceBeginUndoLevel");
					b = ed.selection.getBookmark();
					ed.dom.remove(e, 1);
					ed.selection.moveToBookmark(b);
					tinyMCEPopup.execCommand("mceEndUndoLevel");
					annoLink.close();
				}
				return;
			}

			tinyMCEPopup.execCommand("mceBeginUndoLevel");
			// Leverage the logic of CreateLink
			if (e == null) {
				tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1});

				tinymce.each(ed.dom.select("a"), function(n) {
					if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') {
						var new_element = document.createElement('span');
					 	var old_innerHTML = n.innerHTML;

					 	attrs['class'] = 'ext-link';
					 	attrs['data-xmlel'] = 'ext-link';
					 	attrs['xmlns:xlink'] = 'http://www.w3.org/1999/xlink';
						n.parentNode.replaceChild(new_element, n);
						e = new_element;
						ed.dom.setAttribs(e, attrs);
						e.innerHTML = old_innerHTML;
					}
				});

				// Sometimes WebKit lets a user create a link where
				// they shouldn't be able to. In this case, CreateLink
				// injects "#mce_temp_url#" into their content. Fix it.
				if ( $(e).text() == '#mce_temp_url#' ) {
					ed.dom.remove(e);
					e = null;
				}

			}
			else {
				ed.dom.setAttribs(e, attrs);
			}

			// Don't move caret if selection was image
			if ( e && (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') ) {
				ed.focus();
				ed.selection.select(e);
				ed.selection.collapse(0);
				tinyMCEPopup.storeSelection();
			}

			tinyMCEPopup.execCommand("mceEndUndoLevel");
			annoLink.close();
		},

		setDefaultValues : function() {
			// Set URL and descriptions to defaults.
			// Leave the new tab setting as-is.
			inputs.url.val('http://');
			inputs.title.val('');
		},

		keyup: function( event ) {
			var key = $.ui.keyCode;

			switch( event.which ) {
				case key.ESCAPE:
					event.stopImmediatePropagation();
					if ( ! $(document).triggerHandler( 'wp_CloseOnEscape', [{ event: event, what: 'annolink', cb: annoLink.close }] ) )
						annoLink.close();

					return false;
					break;
				default:
					return;
			}
			event.preventDefault();
		},
	}
	$(document).ready( annoLink.init );
})(jQuery);
<|endoftext|>