<commit_before>var _ = require('underscore');
var StylesFactory = require('../styles-factory');
var FONTS_LIST = [
  'DejaVu Sans Book',
  'unifont Medium',
  'Open Sans Regular',
  'Lato Regular',
  'Lato Bold',
  'Lato Bold Italic',
  'Graduate Regular',
  'Gravitas One Regular',
  'Old Standard TT Regular',
  'Old Standard TT Italic',
  'Old Standard TT Bold'
];

// Depending the type of the style, if could provide different schema values
function getOptionsByStyleType (params) {
  if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
  var optionsArray = getSchemaColumns(params.querySchemaModel, params.filterFunction);

  switch (params.styleType) {
    case 'heatmap':
      optionsArray = [{
        val: 'cartodb_id',
        label: 'cartodb_id',
        type: 'number'
      }];
      break;
    case 'regions':
      optionsArray = [{
        val: 'agg_value',
        label: 'agg_value',
        type: 'number'
      }, {
        val: 'agg_value_density',
        label: 'agg_value_density',
        type: 'number'
      }];
      break;
    case 'hexabins':
    case 'squares':
      optionsArray = [{
        val: 'agg_value',
        label: 'agg_value',
        type: 'number'
      }];
      break;
    case 'animation':
      if (params.animationType === 'heatmap') {
        optionsArray = [{
          val: 'cartodb_id',
          label: 'cartodb_id',
          type: 'number'
        }];
      }
      break;
    default:
      // Nothing
  }

  return optionsArray;
}

// Provide an array of columns from the current schema
function getSchemaColumns (querySchemaModel, filterFunction) {
  if (!querySchemaModel) throw new Error('querySchemaModel is required');

  var columnsCollection = querySchemaModel.columnsCollection;
  if (filterFunction) {
    columnsCollection = columnsCollection.filter(filterFunction);
  }
  return columnsCollection
    .map(function (m) {
      var columnName = m.get('name');
      return {
        val: columnName,
        label: columnName,
        type: m.get('type')
      };
    });
}

function generateSelectByStyleType (params) {
  if (!params.componentName) throw new Error('componentName is required');
  if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
  if (!params.styleType) throw new Error('styleType is required');

  var queryStatus = params.querySchemaModel.get('status');
  var isDisabled = queryStatus !== 'fetched';
  var helpMessage = _t('editor.style.components.' + params.componentName + '.' + queryStatus);

  return {
    type: 'Select',
    title: _t('editor.style.components.' + params.componentName + '.label'),
    placeholder: _t('editor.style.components.' + params.componentName + '.placeholder'),
    help: isDisabled ? helpMessage : '',
    options: getOptionsByStyleType({
      querySchemaModel: params.querySchemaModel,
      filterFunction: params.filterFunction,
      styleType: params.styleType
    }),
    dialogMode: 'float',
    editorAttrs: { disabled: isDisabled },
    validators: ['required']
  };
}

function generateSelectWithSchemaColumns (componentName, querySchemaModel, filterFunction) {
  var queryStatus = querySchemaModel.get('status');
  var isDisabled = queryStatus !== 'fetched';
  var helpMessage = _t('editor.style.components.' + componentName + '.' + queryStatus);

  return {
    type: 'Select',
    title: _t('editor.style.components.' + componentName + '.label'),
    help: isDisabled ? helpMessage : '',
    options: getSchemaColumns(querySchemaModel, filterFunction),
    editorAttrs: { disabled: isDisabled },
    dialogMode: 'float',
    validators: ['required']
  };
}

function generateSimpleStroke (params) {
  if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
  if (!params.configModel) throw new Error('configModel is required');
  if (!params.userModel) throw new Error('userModel is required');
  if (!params.modals) throw new Error('modals is required');

  return {
    type: 'Fill',
    title: _t('editor.style.components.stroke.label'),
    options: [],
    query: params.querySchemaModel.get('query'),
    configModel: params.configModel,
    userModel: params.userModel,
    modals: params.modals,
    dialogMode: 'float',
    editorAttrs: {
      size: {
        min: 0,
        max: 10,
        step: 0.5,
        hidePanes: ['value']
      },
      color: {
        hidePanes: ['value']
      }
    },
    validators: ['required']
  };
}

function generateLineStroke (params) {
  if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
  if (!params.configModel) throw new Error('configModel is required');
  if (!params.styleType) throw new Error('styleType is required');
  if (!params.userModel) throw new Error('userModel is required');
  if (!params.modals) throw new Error('modals is required');

  var queryStatus = params.querySchemaModel.get('status');
  var isDisabled = queryStatus !== 'fetched';
  var helpMessage = _t('editor.style.components.stroke.' + queryStatus);

  return {
    type: 'Fill',
    title: _t('editor.style.components.stroke.label'),
    help: isDisabled ? helpMessage : '',
    options: getOptionsByStyleType({
      querySchemaModel: params.querySchemaModel,
      styleType: params.styleType
    }),
    query: params.querySchemaModel.get('query'),
    configModel: params.configModel,
    userModel: params.userModel,
    modals: params.modals,
    dialogMode: 'float',
    editorAttrs: {
      min: 0,
      max: 50,
      disabled: isDisabled
    },
    validators: ['required']
  };
}

/*
 *  Dictionary that contains all the necessary components
 *  for the styles form
 */

module.exports = {
  'fill': function (params) {
    var editorAttrs = {};
    var options = getOptionsByStyleType({
      querySchemaModel: params.querySchemaModel,
      styleType: params.styleType,
      animationType: params.animationType
    });
    var isPointGeometry = params.queryGeometryModel && params.queryGeometryModel.get('simple_geom') === 'point';
    var isPolygonGeometry = params.queryGeometryModel && params.queryGeometryModel.get('simple_geom') === 'polygon';
    var color = {};
    var size = {
      min: 1,
      max: 45,
      step: 0.5
    };
    var fillLabel = _t('editor.style.components.fill');

    if (params.styleType === 'heatmap') {
      size.hidePanes = ['value'];
      color.hidePanes = ['fixed'];
    } else if (params.styleType === 'simple' && isPointGeometry && !params.isAutoStyleApplied) {
      color.imageEnabled = true;
    }

    if (params.styleType === 'animation') {
      size.hidePanes = ['value'];
      color.hideTabs = ['bins', 'quantification'];

      if (params.animationType === 'simple') {
        color.categorizeColumns = true;
      } else {
        color.hidePanes = ['fixed'];
      }
    }

    if (_.contains(StylesFactory.getAggregationTypes(), params.styleType) || isPolygonGeometry) {
      fillLabel = _t('editor.style.components.color');
    }

    editorAttrs.color = color;
    editorAttrs.size = size;

    return {
      type: 'Fill',
      title: fillLabel,
      options: options,
      query: params.querySchemaModel.get('query'),
      configModel: params.configModel,
      userModel: params.userModel,
      validators: ['required'],
      editorAttrs: editorAttrs,
      modals: params.modals,
      dialogMode: 'float'
    };
  },

  'stroke': function (params) {
    if (params.queryGeometryModel.get('simple_geom') === 'line') {
      return generateLineStroke({
        querySchemaModel: params.querySchemaModel,
        styleType: params.styleType,
        configModel: params.configModel,
        userModel: params.userModel,
        modals: params.modals
      });
    } else {
      return generateSimpleStroke({
        querySchemaModel: params.querySchemaModel,
        configModel: params.configModel,
        userModel: params.userModel,
        modals: params.modals
      });
    }
  },

  'blending': function (params) {
    var animationOptions = ['lighter', 'multiply', 'source-over', 'xor'];
    var simpleOptions = ['none', 'multiply', 'screen', 'overlay', 'darken', 'lighten',
      'color-dodge', 'color-burn', 'xor', 'src-over'];
    var labelTranslate = 'editor.style.components.blending.options';

    var generateBlendingOptions = function (options) {
      return _.reduce(options, function (memo, option) {
        memo.push({
          val: option,
          label: _t(labelTranslate + '.' + option)
        });
        return memo;
      }, []);
    };

    return {
      type: 'Select',
      title: _t('editor.style.components.blending.label'),
      options: generateBlendingOptions(params.styleType === 'animation' ? animationOptions : simpleOptions),
      dialogMode: 'float'
    };
  },

  'style': function () {
    return {
      type: 'Radio',
      title: _t('editor.style.components.type.label'),
      options: [
        {
          val: 'simple',
          label: _t('editor.style.components.type.options.points')
        }, {
          val: 'heatmap',
          label: _t('editor.style.components.type.options.heatmap')
        }
      ]
    };
  },

  'aggregation-size': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.aggregation-size.label'),
      help: _t('editor.style.components.aggregation-size.help'),
      validators: ['required', {
        type: 'interval',
        min: 10,
        max: 100
      }]
    };
  },

  'aggregation-dataset': function () {
    return {
      type: 'Select',
      title: _t('editor.style.components.aggregation-dataset'),
      dialogMode: 'float',
      options: [
        {
          val: 'countries'
        }, {
          val: 'provinces'
        }
      ]
    };
  },

  'aggregation-value': function (params) {
    return {
      type: 'Operators',
      title: _t('editor.style.components.aggregation-value'),
      options: getSchemaColumns(params.querySchemaModel),
      dialogMode: 'float'
    };
  },

  'labels-enabled': function () {
    return {
      type: 'Hidden'
    };
  },

  'labels-attribute': function (params) {
    var filterFunction = function (item) {
      var columnName = item.get('name');
      var columnType = item.get('type');
      return (columnName !== 'the_geom' && columnName !== 'the_geom_webmercator' && columnType !== 'date');
    };
    return generateSelectByStyleType({
      componentName: 'labels-attribute',
      querySchemaModel: params.querySchemaModel,
      styleType: params.styleType,
      filterFunction: filterFunction
    });
  },

  'labels-fill': function () {
    return {
      type: 'Fill',
      title: _t('editor.style.components.labels-fill'),
      options: [],
      dialogMode: 'float',
      editorAttrs: {
        size: {
          min: 6,
          max: 24,
          hidePanes: ['value']
        },
        color: {
          hidePanes: ['value']
        }
      },
      validators: ['required']
    };
  },

  'labels-halo': function () {
    return {
      type: 'Fill',
      title: _t('editor.style.components.labels-halo'),
      options: [],
      dialogMode: 'float',
      editorAttrs: {
        size: {
          hidePanes: ['value']
        },
        color: {
          hidePanes: ['value']
        }
      },
      validators: ['required']
    };
  },

  'labels-font': function () {
    return {
      type: 'Select',
      dialogMode: 'float',
      options: FONTS_LIST
    };
  },

  'labels-offset': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.labels-offset'),
      validators: ['required', {
        type: 'interval',
        min: -15,
        max: 15
      }]
    };
  },

  'labels-overlap': function () {
    return {
      type: 'Radio',
      title: _t('editor.style.components.labels-overlap.label'),
      options: [
        {
          val: 'true',
          label: _t('editor.style.components.labels-overlap.options.true')
        }, {
          val: 'false',
          label: _t('editor.style.components.labels-overlap.options.false')
        }
      ]
    };
  },

  'labels-placement': function () {
    var placements = ['point', 'line', 'vertex', 'interior'];
    var translateLabel = 'editor.style.components.labels-placement.options';

    return {
      type: 'Select',
      title: _t('editor.style.components.labels-placement.label'),
      dialogMode: 'float',
      options: _.reduce(placements, function (memo, type) {
        memo.push({
          val: type,
          label: _t(translateLabel + '.' + type)
        });
        return memo;
      }, []),
      editorAttrs: {
        showSearch: false
      }
    };
  },

  'animated-enabled': function () {
    return {
      type: 'Hidden'
    };
  },

  'animated-attribute': function (params) {
    var filterFunction = function (item) {
      var columnType = item.get('type');
      if (columnType && (columnType === 'number' || columnType === 'date')) {
        return true;
      }
      return false;
    };

    if (params.styleType === 'heatmap') {
      return generateSelectWithSchemaColumns({
        componentName: 'animated-attribute',
        querySchemaModel: params.querySchemaModel,
        filterFunction: filterFunction
      });
    } else {
      return generateSelectByStyleType({
        componentName: 'animated-attribute',
        querySchemaModel: params.querySchemaModel,
        filterFunction: filterFunction,
        styleType: params.styleType
      });
    }
  },

  'animated-overlap': function () {
    return {
      type: 'Radio',
      title: _t('editor.style.components.animated-overlap.label'),
      options: [
        {
          val: 'false',
          label: _t('editor.style.components.animated-overlap.options.false')
        }, {
          val: 'true',
          label: _t('editor.style.components.animated-overlap.options.true')
        }
      ]
    };
  },

  'animated-duration': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.animated-duration'),
      validators: ['required', {
        type: 'interval',
        min: 0,
        max: 60
      }]
    };
  },

  'animated-steps': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.animated-steps'),
      validators: ['required', {
        type: 'interval',
        min: 1,
        max: 1024,
        step: 4
      }]
    };
  },

  'animated-trails': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.animated-trails'),
      validators: ['required', {
        type: 'interval',
        min: 0,
        max: 30
      }]
    };
  },

  'animated-resolution': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.animated-resolution'),
      validators: ['required', {
        type: 'interval',
        min: 1,
        max: 16
      }]
    };
  }
};
<commit_msg>Extract geometry check to a method<commit_after>var _ = require('underscore');
var StylesFactory = require('../styles-factory');
var FONTS_LIST = [
  'DejaVu Sans Book',
  'unifont Medium',
  'Open Sans Regular',
  'Lato Regular',
  'Lato Bold',
  'Lato Bold Italic',
  'Graduate Regular',
  'Gravitas One Regular',
  'Old Standard TT Regular',
  'Old Standard TT Italic',
  'Old Standard TT Bold'
];

// Depending the type of the style, if could provide different schema values
function getOptionsByStyleType (params) {
  if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
  var optionsArray = getSchemaColumns(params.querySchemaModel, params.filterFunction);

  switch (params.styleType) {
    case 'heatmap':
      optionsArray = [{
        val: 'cartodb_id',
        label: 'cartodb_id',
        type: 'number'
      }];
      break;
    case 'regions':
      optionsArray = [{
        val: 'agg_value',
        label: 'agg_value',
        type: 'number'
      }, {
        val: 'agg_value_density',
        label: 'agg_value_density',
        type: 'number'
      }];
      break;
    case 'hexabins':
    case 'squares':
      optionsArray = [{
        val: 'agg_value',
        label: 'agg_value',
        type: 'number'
      }];
      break;
    case 'animation':
      if (params.animationType === 'heatmap') {
        optionsArray = [{
          val: 'cartodb_id',
          label: 'cartodb_id',
          type: 'number'
        }];
      }
      break;
    default:
      // Nothing
  }

  return optionsArray;
}

// Provide an array of columns from the current schema
function getSchemaColumns (querySchemaModel, filterFunction) {
  if (!querySchemaModel) throw new Error('querySchemaModel is required');

  var columnsCollection = querySchemaModel.columnsCollection;
  if (filterFunction) {
    columnsCollection = columnsCollection.filter(filterFunction);
  }
  return columnsCollection
    .map(function (m) {
      var columnName = m.get('name');
      return {
        val: columnName,
        label: columnName,
        type: m.get('type')
      };
    });
}

function generateSelectByStyleType (params) {
  if (!params.componentName) throw new Error('componentName is required');
  if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
  if (!params.styleType) throw new Error('styleType is required');

  var queryStatus = params.querySchemaModel.get('status');
  var isDisabled = queryStatus !== 'fetched';
  var helpMessage = _t('editor.style.components.' + params.componentName + '.' + queryStatus);

  return {
    type: 'Select',
    title: _t('editor.style.components.' + params.componentName + '.label'),
    placeholder: _t('editor.style.components.' + params.componentName + '.placeholder'),
    help: isDisabled ? helpMessage : '',
    options: getOptionsByStyleType({
      querySchemaModel: params.querySchemaModel,
      filterFunction: params.filterFunction,
      styleType: params.styleType
    }),
    dialogMode: 'float',
    editorAttrs: { disabled: isDisabled },
    validators: ['required']
  };
}

function generateSelectWithSchemaColumns (componentName, querySchemaModel, filterFunction) {
  var queryStatus = querySchemaModel.get('status');
  var isDisabled = queryStatus !== 'fetched';
  var helpMessage = _t('editor.style.components.' + componentName + '.' + queryStatus);

  return {
    type: 'Select',
    title: _t('editor.style.components.' + componentName + '.label'),
    help: isDisabled ? helpMessage : '',
    options: getSchemaColumns(querySchemaModel, filterFunction),
    editorAttrs: { disabled: isDisabled },
    dialogMode: 'float',
    validators: ['required']
  };
}

function generateSimpleStroke (params) {
  if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
  if (!params.configModel) throw new Error('configModel is required');
  if (!params.userModel) throw new Error('userModel is required');
  if (!params.modals) throw new Error('modals is required');

  return {
    type: 'Fill',
    title: _t('editor.style.components.stroke.label'),
    options: [],
    query: params.querySchemaModel.get('query'),
    configModel: params.configModel,
    userModel: params.userModel,
    modals: params.modals,
    dialogMode: 'float',
    editorAttrs: {
      size: {
        min: 0,
        max: 10,
        step: 0.5,
        hidePanes: ['value']
      },
      color: {
        hidePanes: ['value']
      }
    },
    validators: ['required']
  };
}

function generateLineStroke (params) {
  if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
  if (!params.configModel) throw new Error('configModel is required');
  if (!params.styleType) throw new Error('styleType is required');
  if (!params.userModel) throw new Error('userModel is required');
  if (!params.modals) throw new Error('modals is required');

  var queryStatus = params.querySchemaModel.get('status');
  var isDisabled = queryStatus !== 'fetched';
  var helpMessage = _t('editor.style.components.stroke.' + queryStatus);

  return {
    type: 'Fill',
    title: _t('editor.style.components.stroke.label'),
    help: isDisabled ? helpMessage : '',
    options: getOptionsByStyleType({
      querySchemaModel: params.querySchemaModel,
      styleType: params.styleType
    }),
    query: params.querySchemaModel.get('query'),
    configModel: params.configModel,
    userModel: params.userModel,
    modals: params.modals,
    dialogMode: 'float',
    editorAttrs: {
      min: 0,
      max: 50,
      disabled: isDisabled
    },
    validators: ['required']
  };
}

function hasGeometryOf (params, type) {
  return params.queryGeometryModel && params.queryGeometryModel.get('simple_geom') === type;
}

/*
 *  Dictionary that contains all the necessary components
 *  for the styles form
 */

module.exports = {
  'fill': function (params) {
    var editorAttrs = {};
    var options = getOptionsByStyleType({
      querySchemaModel: params.querySchemaModel,
      styleType: params.styleType,
      animationType: params.animationType
    });
    var color = {};
    var size = {
      min: 1,
      max: 45,
      step: 0.5
    };
    var fillLabel = _t('editor.style.components.fill');

    if (params.styleType === 'heatmap') {
      size.hidePanes = ['value'];
      color.hidePanes = ['fixed'];
    } else if (params.styleType === 'simple' && hasGeometryOf(params, 'point') && !params.isAutoStyleApplied) {
      color.imageEnabled = true;
    }

    if (params.styleType === 'animation') {
      size.hidePanes = ['value'];
      color.hideTabs = ['bins', 'quantification'];

      if (params.animationType === 'simple') {
        color.categorizeColumns = true;
      } else {
        color.hidePanes = ['fixed'];
      }
    }

    if (_.contains(StylesFactory.getAggregationTypes(), params.styleType) || hasGeometryOf(params, 'polygon')) {
      fillLabel = _t('editor.style.components.color');
    }

    editorAttrs.color = color;
    editorAttrs.size = size;

    return {
      type: 'Fill',
      title: fillLabel,
      options: options,
      query: params.querySchemaModel.get('query'),
      configModel: params.configModel,
      userModel: params.userModel,
      validators: ['required'],
      editorAttrs: editorAttrs,
      modals: params.modals,
      dialogMode: 'float'
    };
  },

  'stroke': function (params) {
    if (params.queryGeometryModel.get('simple_geom') === 'line') {
      return generateLineStroke({
        querySchemaModel: params.querySchemaModel,
        styleType: params.styleType,
        configModel: params.configModel,
        userModel: params.userModel,
        modals: params.modals
      });
    } else {
      return generateSimpleStroke({
        querySchemaModel: params.querySchemaModel,
        configModel: params.configModel,
        userModel: params.userModel,
        modals: params.modals
      });
    }
  },

  'blending': function (params) {
    var animationOptions = ['lighter', 'multiply', 'source-over', 'xor'];
    var simpleOptions = ['none', 'multiply', 'screen', 'overlay', 'darken', 'lighten',
      'color-dodge', 'color-burn', 'xor', 'src-over'];
    var labelTranslate = 'editor.style.components.blending.options';

    var generateBlendingOptions = function (options) {
      return _.reduce(options, function (memo, option) {
        memo.push({
          val: option,
          label: _t(labelTranslate + '.' + option)
        });
        return memo;
      }, []);
    };

    return {
      type: 'Select',
      title: _t('editor.style.components.blending.label'),
      options: generateBlendingOptions(params.styleType === 'animation' ? animationOptions : simpleOptions),
      dialogMode: 'float'
    };
  },

  'style': function () {
    return {
      type: 'Radio',
      title: _t('editor.style.components.type.label'),
      options: [
        {
          val: 'simple',
          label: _t('editor.style.components.type.options.points')
        }, {
          val: 'heatmap',
          label: _t('editor.style.components.type.options.heatmap')
        }
      ]
    };
  },

  'aggregation-size': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.aggregation-size.label'),
      help: _t('editor.style.components.aggregation-size.help'),
      validators: ['required', {
        type: 'interval',
        min: 10,
        max: 100
      }]
    };
  },

  'aggregation-dataset': function () {
    return {
      type: 'Select',
      title: _t('editor.style.components.aggregation-dataset'),
      dialogMode: 'float',
      options: [
        {
          val: 'countries'
        }, {
          val: 'provinces'
        }
      ]
    };
  },

  'aggregation-value': function (params) {
    return {
      type: 'Operators',
      title: _t('editor.style.components.aggregation-value'),
      options: getSchemaColumns(params.querySchemaModel),
      dialogMode: 'float'
    };
  },

  'labels-enabled': function () {
    return {
      type: 'Hidden'
    };
  },

  'labels-attribute': function (params) {
    var filterFunction = function (item) {
      var columnName = item.get('name');
      var columnType = item.get('type');
      return (columnName !== 'the_geom' && columnName !== 'the_geom_webmercator' && columnType !== 'date');
    };
    return generateSelectByStyleType({
      componentName: 'labels-attribute',
      querySchemaModel: params.querySchemaModel,
      styleType: params.styleType,
      filterFunction: filterFunction
    });
  },

  'labels-fill': function () {
    return {
      type: 'Fill',
      title: _t('editor.style.components.labels-fill'),
      options: [],
      dialogMode: 'float',
      editorAttrs: {
        size: {
          min: 6,
          max: 24,
          hidePanes: ['value']
        },
        color: {
          hidePanes: ['value']
        }
      },
      validators: ['required']
    };
  },

  'labels-halo': function () {
    return {
      type: 'Fill',
      title: _t('editor.style.components.labels-halo'),
      options: [],
      dialogMode: 'float',
      editorAttrs: {
        size: {
          hidePanes: ['value']
        },
        color: {
          hidePanes: ['value']
        }
      },
      validators: ['required']
    };
  },

  'labels-font': function () {
    return {
      type: 'Select',
      dialogMode: 'float',
      options: FONTS_LIST
    };
  },

  'labels-offset': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.labels-offset'),
      validators: ['required', {
        type: 'interval',
        min: -15,
        max: 15
      }]
    };
  },

  'labels-overlap': function () {
    return {
      type: 'Radio',
      title: _t('editor.style.components.labels-overlap.label'),
      options: [
        {
          val: 'true',
          label: _t('editor.style.components.labels-overlap.options.true')
        }, {
          val: 'false',
          label: _t('editor.style.components.labels-overlap.options.false')
        }
      ]
    };
  },

  'labels-placement': function () {
    var placements = ['point', 'line', 'vertex', 'interior'];
    var translateLabel = 'editor.style.components.labels-placement.options';

    return {
      type: 'Select',
      title: _t('editor.style.components.labels-placement.label'),
      dialogMode: 'float',
      options: _.reduce(placements, function (memo, type) {
        memo.push({
          val: type,
          label: _t(translateLabel + '.' + type)
        });
        return memo;
      }, []),
      editorAttrs: {
        showSearch: false
      }
    };
  },

  'animated-enabled': function () {
    return {
      type: 'Hidden'
    };
  },

  'animated-attribute': function (params) {
    var filterFunction = function (item) {
      var columnType = item.get('type');
      if (columnType && (columnType === 'number' || columnType === 'date')) {
        return true;
      }
      return false;
    };

    if (params.styleType === 'heatmap') {
      return generateSelectWithSchemaColumns({
        componentName: 'animated-attribute',
        querySchemaModel: params.querySchemaModel,
        filterFunction: filterFunction
      });
    } else {
      return generateSelectByStyleType({
        componentName: 'animated-attribute',
        querySchemaModel: params.querySchemaModel,
        filterFunction: filterFunction,
        styleType: params.styleType
      });
    }
  },

  'animated-overlap': function () {
    return {
      type: 'Radio',
      title: _t('editor.style.components.animated-overlap.label'),
      options: [
        {
          val: 'false',
          label: _t('editor.style.components.animated-overlap.options.false')
        }, {
          val: 'true',
          label: _t('editor.style.components.animated-overlap.options.true')
        }
      ]
    };
  },

  'animated-duration': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.animated-duration'),
      validators: ['required', {
        type: 'interval',
        min: 0,
        max: 60
      }]
    };
  },

  'animated-steps': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.animated-steps'),
      validators: ['required', {
        type: 'interval',
        min: 1,
        max: 1024,
        step: 4
      }]
    };
  },

  'animated-trails': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.animated-trails'),
      validators: ['required', {
        type: 'interval',
        min: 0,
        max: 30
      }]
    };
  },

  'animated-resolution': function () {
    return {
      type: 'Number',
      title: _t('editor.style.components.animated-resolution'),
      validators: ['required', {
        type: 'interval',
        min: 1,
        max: 16
      }]
    };
  }
};
<|endoftext|>


<commit_before>const {
  normalizePath,
  isInHBuilderX
} = require('@dcloudio/uni-cli-shared/lib/util')

class ErrorReport {
  static get instance () {
    if (this._instance == null) {
      this._instance = new ErrorReport()
    }
    return this._instance
  }

  constructor () {
    this._instance = null
    this._https = null
    this._crypto = null
    this._cacheList = []
    this._UNI_INPUT_DIR_REG = new RegExp(normalizePath(process.env.UNI_INPUT_DIR), 'g')
    this._UNI_CLI_CONTEXT_REG = new RegExp(normalizePath(process.env.UNI_CLI_CONTEXT), 'g')
  }

  get https () {
    if (this._https == null) {
      this._https = require('https')
    }
    return this._https
  }

  report (type, err) {
    if (!this._shouldReport(err)) {
      return
    }

    err = normalizePath(err)
    err = err.replace(this._UNI_INPUT_DIR_REG, 'UNI_INPUT_DIR')
    err = err.replace(this._UNI_CLI_CONTEXT_REG, 'UNI_CLI_CONTEXT')

    const data = JSON.stringify({
      np: process.platform,
      nv: process.version,
      cp: process.env.UNI_PLATFORM,
      hx: isInHBuilderX ? 1 : 0,
      et: type,
      em: err
    })

    var hash = this._getMD5(data)

    if (this._cacheList.includes(hash)) {
      return
    }

    this._cacheList.push(hash)

    setTimeout(() => {
      this._doReport(data)
    }, 10)
  }

  _doReport (data) {
    const req = this.https.request({
      hostname: this.HOST,
      port: 443,
      path: this.PATH,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': data.length
      }
    })
    req.write(data)
    req.end()
  }

  _shouldReport (err = '') {
    try {
      const errMsg = err.toString()

      const errorIndex = this.EXCLUDE_ERROR_LIST.findIndex(item => errMsg.includes(item))
      if (errorIndex >= 0) {
        return false
      }

      // 目前简单的上报逻辑为：错误信息中包含@dcloudio包名
      if (
        errMsg.includes('@dcloudio') &&
        !errMsg.includes('Errors compiling template')
      ) {
        return true
      }
    } catch (e) {}
    return false
  }

  _getMD5 (str) {
    return this.crypto.createHash('md5').update(str).digest('hex')
  }

  get crypto () {
    if (this._crypto == null) {
      this._crypto = require('crypto')
    }
    return this._crypto
  }
}
Object.assign(ErrorReport.prototype, {
  HOST: '96f0e031-f37a-48ef-84c7-2023f6360c0a.bspapp.com',
  PATH: '/http/uni-app-compiler',
  EXCLUDE_ERROR_LIST: ['uni-app-compiler']
})

function report (type, err) {
  ErrorReport.instance.report(type, err)
}

global.__error_reporting__ = report

process
  .on('unhandledRejection', (reason, p) => {
    global.__error_reporting__ && global.__error_reporting__('unhandledRejection', reason)
  })
  .on('uncaughtException', err => {
    global.__error_reporting__ && global.__error_reporting__('uncaughtException', err)
  })
<commit_msg>update: 优化编译错误上报<commit_after>const {
  normalizePath,
  isInHBuilderX
} = require('@dcloudio/uni-cli-shared/lib/util')
const plp = require('@dcloudio/webpack-uni-pages-loader/package.json')

class ErrorReport {
  static get instance () {
    if (this._instance == null) {
      this._instance = new ErrorReport()
    }
    return this._instance
  }

  constructor () {
    this._instance = null
    this._os = null
    this._https = null
    this._crypto = null
    this._cacheList = []
    this._UNI_INPUT_DIR_REG = new RegExp(normalizePath(process.env.UNI_INPUT_DIR), 'g')
    this._UNI_CLI_CONTEXT_REG = new RegExp(normalizePath(process.env.UNI_CLI_CONTEXT), 'g')
  }

  get os () {
    if (this._os == null) {
      this._os = require('os')
    }
    return this._os
  }

  get https () {
    if (this._https == null) {
      this._https = require('https')
    }
    return this._https
  }

  report (type, err) {
    if (!this._shouldReport(err)) {
      return
    }

    err = normalizePath(err)
    err = err.replace(this._UNI_INPUT_DIR_REG, 'UNI_INPUT_DIR')
    err = err.replace(this._UNI_CLI_CONTEXT_REG, 'UNI_CLI_CONTEXT')

    const data = JSON.stringify({
      di: this._getMD5(this._getMac()),
      np: process.platform,
      nv: process.version,
      cp: process.env.UNI_PLATFORM,
      cv: plp['uni-app'].compilerVersion,
      hx: isInHBuilderX ? 1 : 0,
      et: type,
      em: err
    })

    var hash = this._getMD5(data)

    if (this._cacheList.includes(hash)) {
      return
    }

    this._cacheList.push(hash)

    setTimeout(() => {
      this._doReport(data)
    }, 10)
  }

  _doReport (data) {
    const req = this.https.request({
      hostname: this.HOST,
      port: 443,
      path: this.PATH,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': data.length
      }
    })
    req.write(data)
    req.end()
  }

  _shouldReport (err = '') {
    try {
      const errMsg = err.toString()

      const errorIndex = this.EXCLUDE_ERROR_LIST.findIndex(item => errMsg.includes(item))
      if (errorIndex >= 0) {
        return false
      }

      // 目前简单的上报逻辑为：错误信息中包含@dcloudio包名
      if (
        errMsg.includes('@dcloudio') &&
        !errMsg.includes('Errors compiling template')
      ) {
        return true
      }
    } catch (e) {}
    return false
  }

  _getMD5 (str) {
    return this.crypto.createHash('md5').update(str).digest('hex')
  }

  _getMac () {
    let mac
    const network = this.os.networkInterfaces()
    for (const key in network) {
      const array = network[key]
      for (let i = 0; i < array.length; i++) {
        const item = array[i]
        if (!item.family || (item.mac && item.mac === '00:00:00:00:00:00')) {
          continue
        }
        if (item.family === 'IPv4' || item.family === 'IPv6') {
          mac = item.mac
          break
        }
      }
    }
    return mac
  }

  get crypto () {
    if (this._crypto == null) {
      this._crypto = require('crypto')
    }
    return this._crypto
  }
}
Object.assign(ErrorReport.prototype, {
  HOST: '96f0e031-f37a-48ef-84c7-2023f6360c0a.bspapp.com',
  PATH: '/http/uni-app-compiler',
  EXCLUDE_ERROR_LIST: ['uni-app-compiler', 'Error: ENOENT: no such file or directory']
})

function report (type, err) {
  ErrorReport.instance.report(type, err)
}

global.__error_reporting__ = report

process
  .on('unhandledRejection', (reason, p) => {
    global.__error_reporting__ && global.__error_reporting__('unhandledRejection', reason)
  })
  .on('uncaughtException', err => {
    global.__error_reporting__ && global.__error_reporting__('uncaughtException', err)
  })
<|endoftext|>


<commit_before>// (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';

import Legend from './Legend';
import Bar from './meter/Bar';
import Spiral from './meter/Spiral';
import Circle from './meter/Circle';
import Arc from './meter/Arc';
import Intl from '../utils/Intl';

const CLASS_ROOT = "meter";

const TYPE_COMPONENT = {
  'bar': Bar,
  'circle': Circle,
  'arc': Arc,
  'spiral': Spiral
};

export default class Meter extends Component {

  constructor (props) {
    super();

    this._initialTimeout = this._initialTimeout.bind(this);
    this._layout = this._layout.bind(this);
    this._onResize = this._onResize.bind(this);
    this._onActivate = this._onActivate.bind(this);

    this.state = this._stateFromProps(props);
    if (this.state.placeLegend) {
      this.state.legendPlacement = 'bottom';
    }
    this.state.initial = true;
  }

  componentDidMount () {
    this._initialTimer = setTimeout(this._initialTimeout, 10);
    window.addEventListener('resize', this._onResize);
    this._onResize();
  }

  componentWillReceiveProps (nextProps) {
    let state = this._stateFromProps(nextProps);
    this.setState(state);
    this._onResize();
  }

  componentWillUnmount () {
    clearTimeout(this._initialTimer);
    clearTimeout(this._resizeTimer);
    window.removeEventListener('resize', this._onResize);
  }

  _initialTimeout () {
    this.setState({
      initial: false,
      activeIndex: this.state.importantIndex
    });
    clearTimeout(this._initialTimer);
  }

  _onActivate (index) {
    if (index === null) {
      index = this.state.importantIndex;
    }
    this.setState({initial: false, activeIndex: index});
  }

  _onResize () {
    // debounce
    clearTimeout(this._resizeTimer);
    this._resizeTimer = setTimeout(this._layout, 50);
  }

  _layout () {
    if (this.state.placeLegend) {
      // legendPlacement based on available window orientation
      let ratio = window.innerWidth / window.innerHeight;
      if (ratio < 0.8) {
        this.setState({legendPlacement: 'bottom'});
      } else if (ratio > 1.2) {
        this.setState({legendPlacement: 'right'});
      }
    }

    if ('right' === this.state.legendPlacement) {
      if (this.refs.legend) {
        let graphicHeight = this.refs.activeGraphic.offsetHeight;
        let legendHeight = ReactDOM.findDOMNode(this.refs.legend).offsetHeight;
        this.setState({tallLegend: (legendHeight > graphicHeight)});
      }
    }
  }

  _normalizeSeries (props, min, max, thresholds) {
    let series = [];
    if (props.series) {
      series = props.series;
    } else if (props.value || props.value === 0) {
      series = [
        {value: props.value, important: true}
      ];
    }

    // set color index
    if (series.length === 1 && props.thresholds) {
      let item = series[0];
      if (! item.colorIndex) {
        // see which threshold color index to use
        let cumulative = 0;
        thresholds.some(function (threshold) {
          cumulative += threshold.value;
          if (item.value < cumulative) {
            item.colorIndex = threshold.colorIndex || 'graph-1';
            return true;
          }
          return false;
        });
      }
    } else {
      series.forEach(function (item, index) {
        if (! item.colorIndex) {
          item.colorIndex = `graph-${index + 1}`;
        }
      });
    }

    return series;
  }

  _normalizeThresholds (props, min, max) {
    let thresholds = [];
    if (props.thresholds) {
      // Convert thresholds from absolute values to cummulative,
      // so we can re-use the series drawing code.
      let priorValue = min.value;
      thresholds.push({ hidden: true });
      for (let i = 0; i < props.thresholds.length; i += 1) {
        let threshold = props.thresholds[i];
        // The value for the prior threshold ends at the beginning of this
        // threshold. Series drawing code expects the end value.
        thresholds[i].value = threshold.value - priorValue;
        thresholds.push({
          label: threshold.label,
          colorIndex: threshold.colorIndex,
          ariaLabel: `${threshold.value} ${props.units || ''} ${threshold.label || ''}`
        });
        priorValue = threshold.value;
        if (i === (props.thresholds.length - 1)) {
          thresholds[thresholds.length-1].value = max.value - priorValue;
        }
      }
    } else if (props.threshold) {
      // let remaining = max.value - props.threshold;
      thresholds = [
        { value: props.threshold, hidden: true },
        {
          value: max.value - props.threshold,
          colorIndex: 'critical',
          ariaLabel: `${props.threshold} ${props.units || ''}`
        }
      ];
    }
    return thresholds;
  }

  _importantIndex (props, series) {
    let result = null;
    if (series.length === 1) {
      result = 0;
    }
    if (props.hasOwnProperty('important')) {
      result = props.important;
    }
    series.some(function (data, index) {
      if (data.important) {
        result = index;
        return true;
      }
      return false;
    });
    return result;
  }

  // Normalize min or max to an object.
  _terminal (terminal) {
    if (typeof terminal === 'number') {
      terminal = {value: terminal};
    }
    return terminal;
  }

  _seriesTotal (series) {
    let total = 0;
    series.some(function (item) {
      total += item.value;
    });
    return total;
  }

  _seriesMax (series) {
    let max = 0;
    series.some(function (item) {
      max = Math.max(max, item.value);
    });
    return max;
  }

  // Generates state based on the provided props.
  _stateFromProps (props) {
    let total;
    if (props.series) {
      total = this._seriesTotal(props.series);
    } else if (props.hasOwnProperty('value')) {
      total = props.value;
    } else {
      total = 0;
    }
    let seriesMax;
    // only care about series max when there are multiple values
    if (props.series && props.series.length > 1) {
      seriesMax = this._seriesMax(props.series);
    }
    // Normalize min and max
    let min = this._terminal(props.min || 0);
    // Max could be provided in props or come from the total of
    // a multi-value series.
    let max = this._terminal(props.max ||
      (props.stacked ? Math.max(seriesMax, total || 0, 100) :
        (seriesMax || Math.max(total || 0, 100))));
    // Normalize simple threshold prop to an array, if needed.
    let thresholds = this._normalizeThresholds(props, min, max);
    // Normalize simple value prop to a series, if needed.
    let series = this._normalizeSeries(props, min, max, thresholds);
    // Determine important index.
    let importantIndex = this._importantIndex(props, series);

    let state = {
      importantIndex: importantIndex,
      activeIndex: importantIndex,
      series: series,
      thresholds: thresholds,
      min: min,
      max: max,
      total: total
    };

    // legend
    state.placeLegend = ! (props.legend && props.legend.placement);
    if (! state.placeLegend) {
      state.legendPlacement = props.legend.placement;
    }

    return state;
  }

  _getActiveFields () {
    let fields;
    if (null === this.state.activeIndex) {
      fields = {
        value: this.state.total,
        label: Intl.getMessage(this.context.intl, 'Total')
      };
    } else {
      let active = this.state.series[this.state.activeIndex];
      if (!active) {
        active = this.state.series[0];
      }
      fields = {
        value: active.value,
        label: active.label,
        onClick: active.onClick
      };
    }
    return fields;
  }

  _renderActiveValue () {
    let fields = this._getActiveFields();
    let classes = [`${CLASS_ROOT}__value`];
    if (fields.onClick) {
      classes.push(`${CLASS_ROOT}__value--active`);
    }
    let units;
    if (this.props.units) {
      units = (
        <span className={`${CLASS_ROOT}__value-units large-number-font`}>
          {this.props.units}

      );
    }

    return (

        <span
          className={`${CLASS_ROOT}__value-value large-number-font`}>
          {fields.value}
          {units}

        <span className={`${CLASS_ROOT}__value-label`}>
          {fields.label}

    );
  }

  _renderMinMax (classes) {
    let minLabel;
    if (this.state.min.label) {
      minLabel = (
        <div className={`${CLASS_ROOT}__minmax-min`}>
          {this.state.min.label}

      );
    }
    let maxLabel;
    if (this.state.max.label) {
      maxLabel = (
        <div className={`${CLASS_ROOT}__minmax-max`}>
          {this.state.max.label}

      );
    }
    let minMax;
    if (minLabel || maxLabel) {
      minMax = (
        <div className={`${CLASS_ROOT}__minmax-container`}>
          <div className={`${CLASS_ROOT}__minmax`}>
            {minLabel}
            {maxLabel}

      );
      classes.push(`${CLASS_ROOT}--minmax`);
    }
    return minMax;
  }

  _renderLegend () {
    let total = (typeof this.props.legend === 'object' && this.props.legend.total);
    return (
      <Legend ref="legend" className={`${CLASS_ROOT}__legend`}
        series={this.state.series}
        units={this.props.units}
        total={total}
        activeIndex={this.state.activeIndex}
        onActive={this._onActivate} />
    );
  }

  render () {
    let classes = [CLASS_ROOT];
    classes.push(`${CLASS_ROOT}--${this.props.type}`);
    if (this.props.vertical) {
      classes.push(`${CLASS_ROOT}--vertical`);
    }
    if (this.props.stacked) {
      classes.push(`${CLASS_ROOT}--stacked`);
    }
    if (this.props.size) {
      classes.push(`${CLASS_ROOT}--${this.props.size}`);
    }
    if (this.state.series.length === 0) {
      classes.push(`${CLASS_ROOT}--loading`);
    } else if (this.state.series.length === 1) {
      classes.push(`${CLASS_ROOT}--single`);
    } else {
      classes.push(`${CLASS_ROOT}--count-${this.state.series.length}`);
    }
    if (this.state.activeIndex !== null) {
      classes.push(`${CLASS_ROOT}--active`);
    }
    if (this.state.tallLegend) {
      classes.push(`${CLASS_ROOT}--tall-legend`);
    }
    if (this.props.className) {
      classes.push(this.props.className);
    }

    let minMax = this._renderMinMax(classes);
    let activeValue;
    if (this.state.series.length > 0) {
      activeValue = this._renderActiveValue();
    }
    let legend;
    let a11yRole;

    if (this.props.legend || this.props.series) {
      a11yRole = 'tablist';

      if (this.props.legend) {
        if ('inline' !== this.props.legend.placement) {
          legend = this._renderLegend();
        } else {
          // Hide value (displaying total), if legend is inline
          // and total is set to false
          if (!(this.props.legend.total)) {
            activeValue = null;
          }
        }
        classes.push(`${CLASS_ROOT}--legend-${this.state.legendPlacement}`);
        if (this.props.legend.align) {
          classes.push(`${CLASS_ROOT}--legend-align-${this.props.legend.align}`);
        }
      }
    }

    let GraphicComponent = TYPE_COMPONENT[this.props.type];
    let graphic = (
      <GraphicComponent
        a11yTitle={this.props.a11yTitle}
        a11yTitleId={this.props.a11yTitleId}
        a11yDesc={this.props.a11yDesc}
        a11yDescId={this.props.a11yDescId}
        a11yRole={a11yRole}
        activeIndex={this.state.activeIndex}
        min={this.state.min} max={this.state.max}
        legend={this.props.legend}
        onActivate={this._onActivate}
        series={this.state.series}
        stacked={this.props.stacked}
        thresholds={this.state.thresholds}
        total={this.state.total}
        units={this.props.units}
        vertical={this.props.vertical} />
    );

    const graphicContainer = (
      <div className={`${CLASS_ROOT}__graphic-container`}>
        {graphic}
        {minMax}

    );

    return (

        <div ref="activeGraphic" className={`${CLASS_ROOT}__value-container`}>
          {graphicContainer}
          {activeValue}

        {legend}

    );
  }

}

Meter.propTypes = {
  a11yTitle: PropTypes.string,
  a11yTitleId: PropTypes.string,
  a11yDescId: PropTypes.string,
  a11yDesc: PropTypes.string,
  important: PropTypes.number,
  legend: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.shape({
      align: PropTypes.oneOf(['start', 'center', 'end']),
      placement: PropTypes.oneOf(['right', 'bottom', 'inline']),
      total: PropTypes.bool
    })
  ]),
  max: PropTypes.oneOfType([
    PropTypes.shape({
      value: PropTypes.number.isRequired,
      label: PropTypes.string
    }),
    PropTypes.number
  ]),
  min: PropTypes.oneOfType([
    PropTypes.shape({
      value: PropTypes.number.isRequired,
      label: PropTypes.string
    }),
    PropTypes.number
  ]),
  size: PropTypes.oneOf(['small', 'medium', 'large', 'xlarge']),
  series: PropTypes.arrayOf(PropTypes.shape({
    label: PropTypes.string,
    value: PropTypes.number.isRequired,
    colorIndex: PropTypes.string,
    important: PropTypes.bool,
    onClick: PropTypes.func
  })),
  stacked: PropTypes.bool,
  threshold: PropTypes.number,
  thresholds: PropTypes.arrayOf(PropTypes.shape({
    label: PropTypes.string,
    value: PropTypes.number.isRequired,
    colorIndex: PropTypes.string
  })),
  type: PropTypes.oneOf(['bar', 'arc', 'circle', 'spiral']),
  units: PropTypes.string,
  value: PropTypes.number,
  vertical: PropTypes.bool
};

Meter.defaultProps = {
  a11yTitleId: 'meter-title',
  a11yDescId: 'meter-desc',
  type: 'bar'
};

Meter.contextTypes = {
  intl: PropTypes.object
};
<commit_msg>Adding responsive watcher to Meter (and responsive option), so large/xlarge Meters can shrink to medium on mobile.<commit_after>// (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';

import Responsive from '../utils/Responsive';
import Legend from './Legend';
import Bar from './meter/Bar';
import Spiral from './meter/Spiral';
import Circle from './meter/Circle';
import Arc from './meter/Arc';
import Intl from '../utils/Intl';

const CLASS_ROOT = "meter";

const TYPE_COMPONENT = {
  'bar': Bar,
  'circle': Circle,
  'arc': Arc,
  'spiral': Spiral
};

export default class Meter extends Component {

  constructor (props) {
    super();

    this._onResponsive = this._onResponsive.bind(this);
    this._initialTimeout = this._initialTimeout.bind(this);
    this._layout = this._layout.bind(this);
    this._onResize = this._onResize.bind(this);
    this._onActivate = this._onActivate.bind(this);

    this.state = this._stateFromProps(props);
    if (this.state.placeLegend) {
      this.state.legendPlacement = 'bottom';
    }
    this.state.initial = true;
    this.state.limitMeterSize = false;
  }

  componentDidMount () {
    if (this.props.responsive) {
      this._responsive = Responsive.start(this._onResponsive);
    }

    this._initialTimer = setTimeout(this._initialTimeout, 10);
    window.addEventListener('resize', this._onResize);
    this._onResize();
  }

  componentWillReceiveProps (nextProps) {
    let state = this._stateFromProps(nextProps);
    this.setState(state);
    this._onResize();
  }

  componentWillUnmount () {
    clearTimeout(this._initialTimer);
    clearTimeout(this._resizeTimer);
    window.removeEventListener('resize', this._onResize);

    if (this._responsive) {
      this._responsive.stop();
    }
  }

  _initialTimeout () {
    this.setState({
      initial: false,
      activeIndex: this.state.importantIndex
    });
    clearTimeout(this._initialTimer);
  }

  _onResponsive (small) {
    if (small) {
      this.setState({limitMeterSize: true});
    } else {
      this.setState({limitMeterSize: false});
    }
  }

  _onActivate (index) {
    if (index === null) {
      index = this.state.importantIndex;
    }
    this.setState({initial: false, activeIndex: index});
  }

  _onResize () {
    // debounce
    clearTimeout(this._resizeTimer);
    this._resizeTimer = setTimeout(this._layout, 50);
  }

  _layout () {
    if (this.state.placeLegend) {
      // legendPlacement based on available window orientation
      let ratio = window.innerWidth / window.innerHeight;
      if (ratio < 0.8) {
        this.setState({legendPlacement: 'bottom'});
      } else if (ratio > 1.2) {
        this.setState({legendPlacement: 'right'});
      }
    }

    if ('right' === this.state.legendPlacement) {
      if (this.refs.legend) {
        let graphicHeight = this.refs.activeGraphic.offsetHeight;
        let legendHeight = ReactDOM.findDOMNode(this.refs.legend).offsetHeight;
        this.setState({tallLegend: (legendHeight > graphicHeight)});
      }
    }
  }

  _normalizeSeries (props, min, max, thresholds) {
    let series = [];
    if (props.series) {
      series = props.series;
    } else if (props.value || props.value === 0) {
      series = [
        {value: props.value, important: true}
      ];
    }

    // set color index
    if (series.length === 1 && props.thresholds) {
      let item = series[0];
      if (! item.colorIndex) {
        // see which threshold color index to use
        let cumulative = 0;
        thresholds.some(function (threshold) {
          cumulative += threshold.value;
          if (item.value < cumulative) {
            item.colorIndex = threshold.colorIndex || 'graph-1';
            return true;
          }
          return false;
        });
      }
    } else {
      series.forEach(function (item, index) {
        if (! item.colorIndex) {
          item.colorIndex = `graph-${index + 1}`;
        }
      });
    }

    return series;
  }

  _normalizeThresholds (props, min, max) {
    let thresholds = [];
    if (props.thresholds) {
      // Convert thresholds from absolute values to cummulative,
      // so we can re-use the series drawing code.
      let priorValue = min.value;
      thresholds.push({ hidden: true });
      for (let i = 0; i < props.thresholds.length; i += 1) {
        let threshold = props.thresholds[i];
        // The value for the prior threshold ends at the beginning of this
        // threshold. Series drawing code expects the end value.
        thresholds[i].value = threshold.value - priorValue;
        thresholds.push({
          label: threshold.label,
          colorIndex: threshold.colorIndex,
          ariaLabel: `${threshold.value} ${props.units || ''} ${threshold.label || ''}`
        });
        priorValue = threshold.value;
        if (i === (props.thresholds.length - 1)) {
          thresholds[thresholds.length-1].value = max.value - priorValue;
        }
      }
    } else if (props.threshold) {
      // let remaining = max.value - props.threshold;
      thresholds = [
        { value: props.threshold, hidden: true },
        {
          value: max.value - props.threshold,
          colorIndex: 'critical',
          ariaLabel: `${props.threshold} ${props.units || ''}`
        }
      ];
    }
    return thresholds;
  }

  _importantIndex (props, series) {
    let result = null;
    if (series.length === 1) {
      result = 0;
    }
    if (props.hasOwnProperty('important')) {
      result = props.important;
    }
    series.some(function (data, index) {
      if (data.important) {
        result = index;
        return true;
      }
      return false;
    });
    return result;
  }

  // Normalize min or max to an object.
  _terminal (terminal) {
    if (typeof terminal === 'number') {
      terminal = {value: terminal};
    }
    return terminal;
  }

  _seriesTotal (series) {
    let total = 0;
    series.some(function (item) {
      total += item.value;
    });
    return total;
  }

  _seriesMax (series) {
    let max = 0;
    series.some(function (item) {
      max = Math.max(max, item.value);
    });
    return max;
  }

  // Generates state based on the provided props.
  _stateFromProps (props) {
    let total;
    if (props.series) {
      total = this._seriesTotal(props.series);
    } else if (props.hasOwnProperty('value')) {
      total = props.value;
    } else {
      total = 0;
    }
    let seriesMax;
    // only care about series max when there are multiple values
    if (props.series && props.series.length > 1) {
      seriesMax = this._seriesMax(props.series);
    }
    // Normalize min and max
    let min = this._terminal(props.min || 0);
    // Max could be provided in props or come from the total of
    // a multi-value series.
    let max = this._terminal(props.max ||
      (props.stacked ? Math.max(seriesMax, total || 0, 100) :
        (seriesMax || Math.max(total || 0, 100))));
    // Normalize simple threshold prop to an array, if needed.
    let thresholds = this._normalizeThresholds(props, min, max);
    // Normalize simple value prop to a series, if needed.
    let series = this._normalizeSeries(props, min, max, thresholds);
    // Determine important index.
    let importantIndex = this._importantIndex(props, series);

    let state = {
      importantIndex: importantIndex,
      activeIndex: importantIndex,
      series: series,
      thresholds: thresholds,
      min: min,
      max: max,
      total: total
    };

    // legend
    state.placeLegend = ! (props.legend && props.legend.placement);
    if (! state.placeLegend) {
      state.legendPlacement = props.legend.placement;
    }

    return state;
  }

  _getActiveFields () {
    let fields;
    if (null === this.state.activeIndex) {
      fields = {
        value: this.state.total,
        label: Intl.getMessage(this.context.intl, 'Total')
      };
    } else {
      let active = this.state.series[this.state.activeIndex];
      if (!active) {
        active = this.state.series[0];
      }
      fields = {
        value: active.value,
        label: active.label,
        onClick: active.onClick
      };
    }
    return fields;
  }

  _renderActiveValue () {
    let fields = this._getActiveFields();
    let classes = [`${CLASS_ROOT}__value`];
    if (fields.onClick) {
      classes.push(`${CLASS_ROOT}__value--active`);
    }
    let units;
    if (this.props.units) {
      units = (
        <span className={`${CLASS_ROOT}__value-units large-number-font`}>
          {this.props.units}

      );
    }

    return (

        <span
          className={`${CLASS_ROOT}__value-value large-number-font`}>
          {fields.value}
          {units}

        <span className={`${CLASS_ROOT}__value-label`}>
          {fields.label}

    );
  }

  _renderMinMax (classes) {
    let minLabel;
    if (this.state.min.label) {
      minLabel = (
        <div className={`${CLASS_ROOT}__minmax-min`}>
          {this.state.min.label}

      );
    }
    let maxLabel;
    if (this.state.max.label) {
      maxLabel = (
        <div className={`${CLASS_ROOT}__minmax-max`}>
          {this.state.max.label}

      );
    }
    let minMax;
    if (minLabel || maxLabel) {
      minMax = (
        <div className={`${CLASS_ROOT}__minmax-container`}>
          <div className={`${CLASS_ROOT}__minmax`}>
            {minLabel}
            {maxLabel}

      );
      classes.push(`${CLASS_ROOT}--minmax`);
    }
    return minMax;
  }

  _renderLegend () {
    let total = (typeof this.props.legend === 'object' && this.props.legend.total);
    return (
      <Legend ref="legend" className={`${CLASS_ROOT}__legend`}
        series={this.state.series}
        units={this.props.units}
        total={total}
        activeIndex={this.state.activeIndex}
        onActive={this._onActivate} />
    );
  }

  render () {
    let classes = [CLASS_ROOT];
    classes.push(`${CLASS_ROOT}--${this.props.type}`);
    if (this.props.vertical) {
      classes.push(`${CLASS_ROOT}--vertical`);
    }
    if (this.props.stacked) {
      classes.push(`${CLASS_ROOT}--stacked`);
    }
    if (this.props.size) {
      let responsiveSize = this.props.size;
      // shrink Meter to medium size if large and up
      if (this.state.limitMeterSize && (this.props.size === 'large' || this.props.size === 'xlarge')) {
        responsiveSize = 'medium';
      }
      classes.push(`${CLASS_ROOT}--${responsiveSize}`);
    }
    if (this.state.series.length === 0) {
      classes.push(`${CLASS_ROOT}--loading`);
    } else if (this.state.series.length === 1) {
      classes.push(`${CLASS_ROOT}--single`);
    } else {
      classes.push(`${CLASS_ROOT}--count-${this.state.series.length}`);
    }
    if (this.state.activeIndex !== null) {
      classes.push(`${CLASS_ROOT}--active`);
    }
    if (this.state.tallLegend) {
      classes.push(`${CLASS_ROOT}--tall-legend`);
    }
    if (this.props.className) {
      classes.push(this.props.className);
    }

    let minMax = this._renderMinMax(classes);
    let activeValue;
    if (this.state.series.length > 0) {
      activeValue = this._renderActiveValue();
    }
    let legend;
    let a11yRole;

    if (this.props.legend || this.props.series) {
      a11yRole = 'tablist';

      if (this.props.legend) {
        if ('inline' !== this.props.legend.placement) {
          legend = this._renderLegend();
        } else {
          // Hide value (displaying total), if legend is inline
          // and total is set to false
          if (!(this.props.legend.total)) {
            activeValue = null;
          }
        }
        classes.push(`${CLASS_ROOT}--legend-${this.state.legendPlacement}`);
        if (this.props.legend.align) {
          classes.push(`${CLASS_ROOT}--legend-align-${this.props.legend.align}`);
        }
      }
    }

    let GraphicComponent = TYPE_COMPONENT[this.props.type];
    let graphic = (
      <GraphicComponent
        a11yTitle={this.props.a11yTitle}
        a11yTitleId={this.props.a11yTitleId}
        a11yDesc={this.props.a11yDesc}
        a11yDescId={this.props.a11yDescId}
        a11yRole={a11yRole}
        activeIndex={this.state.activeIndex}
        min={this.state.min} max={this.state.max}
        legend={this.props.legend}
        onActivate={this._onActivate}
        series={this.state.series}
        stacked={this.props.stacked}
        thresholds={this.state.thresholds}
        total={this.state.total}
        units={this.props.units}
        vertical={this.props.vertical} />
    );

    const graphicContainer = (
      <div className={`${CLASS_ROOT}__graphic-container`}>
        {graphic}
        {minMax}

    );

    return (

        <div ref="activeGraphic" className={`${CLASS_ROOT}__value-container`}>
          {graphicContainer}
          {activeValue}

        {legend}

    );
  }

}

Meter.propTypes = {
  a11yTitle: PropTypes.string,
  a11yTitleId: PropTypes.string,
  a11yDescId: PropTypes.string,
  a11yDesc: PropTypes.string,
  important: PropTypes.number,
  legend: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.shape({
      align: PropTypes.oneOf(['start', 'center', 'end']),
      placement: PropTypes.oneOf(['right', 'bottom', 'inline']),
      total: PropTypes.bool
    })
  ]),
  max: PropTypes.oneOfType([
    PropTypes.shape({
      value: PropTypes.number.isRequired,
      label: PropTypes.string
    }),
    PropTypes.number
  ]),
  min: PropTypes.oneOfType([
    PropTypes.shape({
      value: PropTypes.number.isRequired,
      label: PropTypes.string
    }),
    PropTypes.number
  ]),
  size: PropTypes.oneOf(['small', 'medium', 'large', 'xlarge']),
  series: PropTypes.arrayOf(PropTypes.shape({
    label: PropTypes.string,
    value: PropTypes.number.isRequired,
    colorIndex: PropTypes.string,
    important: PropTypes.bool,
    onClick: PropTypes.func
  })),
  stacked: PropTypes.bool,
  threshold: PropTypes.number,
  thresholds: PropTypes.arrayOf(PropTypes.shape({
    label: PropTypes.string,
    value: PropTypes.number.isRequired,
    colorIndex: PropTypes.string
  })),
  type: PropTypes.oneOf(['bar', 'arc', 'circle', 'spiral']),
  units: PropTypes.string,
  value: PropTypes.number,
  vertical: PropTypes.bool,
  responsive: PropTypes.bool
};

Meter.defaultProps = {
  a11yTitleId: 'meter-title',
  a11yDescId: 'meter-desc',
  type: 'bar'
};

Meter.contextTypes = {
  intl: PropTypes.object
};
<|endoftext|>


<commit_before>var TouchPoint;

(function () {

  'use strict';

  TouchPoint = {

    isSafari: !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/),
    clickTap: ('ontouchstart' in window ? 'touchstart' : 'click'),
    el: 'div',
    dom: window,
    styleEl: '',
    color: '#FFF',
    opacity: 0.8,
    size: 20,
    scale: 8,
    tp: '',
    z: 9999,

    init: function(el) {
      TouchPoint.dom = el;

      TouchPoint
        .createCss('.tp-init',
          'position: relative; width: ' + TouchPoint.size + 'px; height: ' + TouchPoint.size + 'px; background-color: ' + TouchPoint.color + '; opacity: ' + TouchPoint.opacity + '; border-radius: 20px; -ms-transform: scale(0.5); -webkit-transform: scale(0.5); -moz-transform: scale(0.5); -o-transform: scale(0.5); transform: scale(0.5); -ms-transition: all 0.5s ease-out; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; transition: all 0.5s ease-out; z-index: ' + TouchPoint.z + ';')
        .createCss('.tp-anim',
          '-ms-transform: scale(' + TouchPoint.scale + '); -webkit-transform: scale(' + TouchPoint.scale + '); -moz-transform: scale(' + TouchPoint.scale + '); -o-transform: scale(' + TouchPoint.scale + '); transform: scale(' + TouchPoint.scale + '); opacity: 0;'
        );
      TouchPoint.dom.addEventListener(TouchPoint.clickTap, TouchPoint.create, false);
    },

    create: function(e) {
      TouchPoint.dom.removeEventListener(TouchPoint.clickTap, TouchPoint.create, false);

      TouchPoint.tp = document.createElement(TouchPoint.el);
      TouchPoint.tp.setAttribute('id', 'touchpoint');

      if(TouchPoint.getMobileOS() === 'iOS') {
        TouchPoint.tp.style.left = (e.pageX - (TouchPoint.size * 0.5)) + 'px';
        TouchPoint.tp.style.top = (e.pageY - (TouchPoint.size * 0.5)) + 'px';
      } else if (TouchPoint.getMobileOS() === 'Android') {
        TouchPoint.tp.style.left = (e.touches[0].pageX - (TouchPoint.size * 0.5)) + 'px';
        TouchPoint.tp.style.top = (e.touches[0].pageY - (TouchPoint.size * 0.5)) + 'px';
      } else {
        TouchPoint.tp.style.left = (e.clientX - (TouchPoint.size * 0.5)) + 'px';
        TouchPoint.tp.style.top = (e.clientY - (TouchPoint.size * 0.5)) + 'px';
      }
      TouchPoint.tp.className = 'tp-init';

      document.body.appendChild(TouchPoint.tp);

      requestNextAnimationFrame(function () {
        TouchPoint.tp.className += ' tp-anim';
      });
      TouchPoint.tp.addEventListener('transitionend', TouchPoint.gc, false);

      return TouchPoint;
    },

    gc: function(e) {
      var currTP = document.body.querySelector('#touchpoint');

      if(currTP) {
        e.target.removeEventListener('transitionend', TouchPoint.gc, false);
        document.body.removeChild(currTP);
        TouchPoint.reInit();
      }
    },

    reInit: function() {
      TouchPoint.dom.addEventListener(TouchPoint.clickTap, TouchPoint.create, false);
    },

    createCss: function(name, rules) {
      var head = document.head || document.getElementsByTagName('head')[0];

      for(var i = 0; i < head.childNodes.length; i = i + 1) {
        if(head.getElementsByTagName('style')[i].tagName.toLowerCase() === 'style') {
          head.getElementsByTagName('style')[i].innerHTML += name + ' { ' + rules + ' }';
          TouchPoint.styleEl = head.getElementsByTagName('style')[i];
          break;
        } else {
          TouchPoint.styleEl = document.createElement('style');
          TouchPoint.styleEl.type = 'text/css';
          TouchPoint.styleEl.innerHTML = name + ' { ' + rules + ' }';
          head.appendChild(TouchPoint.styleEl);
          break;
        }
      }
      return TouchPoint;
    },

    getMobileOS: function() {
      var userAgent = navigator.userAgent || navigator.vendor || window.opera;

      if(userAgent.match( /iPad/i ) || userAgent.match( /iPhone/i ) || userAgent.match( /iPod/i )) {
        return 'iOS';
      } else if(userAgent.match( /Android/i )) {
        return 'Android';
      } else {
        return 'unknown';
      }
    }
  };
  // requestNextAnimationFrame shim from: https://gist.github.com/getify/3004342
  var ids = {};

  function requestId() {
      var id;
      do {
          id = Math.floor(Math.random() * 1E9);
      } while (id in ids);
      return id;
  }

  if (!window.requestNextAnimationFrame) {
      window.requestNextAnimationFrame = function (callback, element) {
          var id = requestId();

          ids[id] = requestAnimationFrame(function () {
              ids[id] = requestAnimationFrame(function (ts) {
                  delete ids[id];
                  callback(ts);
              }, element);
          }, element);

          return id;
      };
  }

  if (!window.cancelNextAnimationFrame) {
      window.cancelNextAnimationFrame = function (id) {
          if (ids[id]) {
              cancelAnimationFrame(ids[id]);
              delete ids[id];
          }
      };
  }
})();<commit_msg>Support desktop with multi-touch too (#7)<commit_after>var TouchPoint;

(function () {

  'use strict';

  TouchPoint = {

    isSafari: !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/),
    clickTap: ('ontouchstart' in window ? 'touchstart' : 'click'),
    el: 'div',
    dom: window,
    styleEl: '',
    color: '#FFF',
    opacity: 0.8,
    size: 20,
    scale: 8,
    tp: '',
    z: 9999,

    init: function(el) {
      TouchPoint.dom = el;

      TouchPoint
        .createCss('.tp-init',
          'position: relative; width: ' + TouchPoint.size + 'px; height: ' + TouchPoint.size + 'px; background-color: ' + TouchPoint.color + '; opacity: ' + TouchPoint.opacity + '; border-radius: 20px; -ms-transform: scale(0.5); -webkit-transform: scale(0.5); -moz-transform: scale(0.5); -o-transform: scale(0.5); transform: scale(0.5); -ms-transition: all 0.5s ease-out; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; transition: all 0.5s ease-out; z-index: ' + TouchPoint.z + ';')
        .createCss('.tp-anim',
          '-ms-transform: scale(' + TouchPoint.scale + '); -webkit-transform: scale(' + TouchPoint.scale + '); -moz-transform: scale(' + TouchPoint.scale + '); -o-transform: scale(' + TouchPoint.scale + '); transform: scale(' + TouchPoint.scale + '); opacity: 0;'
        );
      TouchPoint.dom.addEventListener(TouchPoint.clickTap, TouchPoint.create, false);
    },

    create: function(e) {
      TouchPoint.dom.removeEventListener(TouchPoint.clickTap, TouchPoint.create, false);

      TouchPoint.tp = document.createElement(TouchPoint.el);
      TouchPoint.tp.setAttribute('id', 'touchpoint');

      if(TouchPoint.getMobileOS() === 'iOS') {
        TouchPoint.tp.style.left = (e.pageX - (TouchPoint.size * 0.5)) + 'px';
        TouchPoint.tp.style.top = (e.pageY - (TouchPoint.size * 0.5)) + 'px';
      } else if (TouchPoint.getMobileOS() === 'Android') {
        TouchPoint.tp.style.left = (e.touches[0].pageX - (TouchPoint.size * 0.5)) + 'px';
        TouchPoint.tp.style.top = (e.touches[0].pageY - (TouchPoint.size * 0.5)) + 'px';
      } else if (e.touches && e.touches.length > 0) {
        TouchPoint.tp.style.left = (e.touches[0].pageX - (TouchPoint.size * 0.5)) + 'px';
        TouchPoint.tp.style.top = (e.touches[0].pageY - (TouchPoint.size * 0.5)) + 'px';
      } else {
        TouchPoint.tp.style.left = (e.clientX - (TouchPoint.size * 0.5)) + 'px';
        TouchPoint.tp.style.top = (e.clientY - (TouchPoint.size * 0.5)) + 'px';
      }
      TouchPoint.tp.className = 'tp-init';

      document.body.appendChild(TouchPoint.tp);

      requestNextAnimationFrame(function () {
        TouchPoint.tp.className += ' tp-anim';
      });
      TouchPoint.tp.addEventListener('transitionend', TouchPoint.gc, false);

      return TouchPoint;
    },

    gc: function(e) {
      var currTP = document.body.querySelector('#touchpoint');

      if(currTP) {
        e.target.removeEventListener('transitionend', TouchPoint.gc, false);
        document.body.removeChild(currTP);
        TouchPoint.reInit();
      }
    },

    reInit: function() {
      TouchPoint.dom.addEventListener(TouchPoint.clickTap, TouchPoint.create, false);
    },

    createCss: function(name, rules) {
      var head = document.head || document.getElementsByTagName('head')[0];

      for(var i = 0; i < head.childNodes.length; i = i + 1) {
        if(head.getElementsByTagName('style')[i].tagName.toLowerCase() === 'style') {
          head.getElementsByTagName('style')[i].innerHTML += name + ' { ' + rules + ' }';
          TouchPoint.styleEl = head.getElementsByTagName('style')[i];
          break;
        } else {
          TouchPoint.styleEl = document.createElement('style');
          TouchPoint.styleEl.type = 'text/css';
          TouchPoint.styleEl.innerHTML = name + ' { ' + rules + ' }';
          head.appendChild(TouchPoint.styleEl);
          break;
        }
      }
      return TouchPoint;
    },

    getMobileOS: function() {
      var userAgent = navigator.userAgent || navigator.vendor || window.opera;

      if(userAgent.match( /iPad/i ) || userAgent.match( /iPhone/i ) || userAgent.match( /iPod/i )) {
        return 'iOS';
      } else if(userAgent.match( /Android/i )) {
        return 'Android';
      } else {
        return 'unknown';
      }
    }
  };
  // requestNextAnimationFrame shim from: https://gist.github.com/getify/3004342
  var ids = {};

  function requestId() {
      var id;
      do {
          id = Math.floor(Math.random() * 1E9);
      } while (id in ids);
      return id;
  }

  if (!window.requestNextAnimationFrame) {
      window.requestNextAnimationFrame = function (callback, element) {
          var id = requestId();

          ids[id] = requestAnimationFrame(function () {
              ids[id] = requestAnimationFrame(function (ts) {
                  delete ids[id];
                  callback(ts);
              }, element);
          }, element);

          return id;
      };
  }

  if (!window.cancelNextAnimationFrame) {
      window.cancelNextAnimationFrame = function (id) {
          if (ids[id]) {
              cancelAnimationFrame(ids[id]);
              delete ids[id];
          }
      };
  }
})();
<|endoftext|>


<commit_before>// (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import FormattedMessage from './FormattedMessage';
import CSSClassnames from '../utils/CSSClassnames';

const CLASS_ROOT = CSSClassnames.CONTROL_ICON;
const COLOR_INDEX = CSSClassnames.COLOR_INDEX;

export default class Icon extends Component {
  render () {
    const { a11yTitleId, className, colorIndex } = this.props;
    let { a11yTitle, size } = this.props;

    const classes = classnames(
      CLASS_ROOT,
      `${CLASS_ROOT}-watch`,
      className,
      {
        [`${CLASS_ROOT}--${size}`]: size,
        [`${COLOR_INDEX}-${colorIndex}`]: colorIndex
      }
    );

    a11yTitle = a11yTitle ||
      <FormattedMessage id="watch" defaultMessage="watch" />;

    return <svg version="1.1" viewBox="0 0 24 24" width="24px" height="24px" role="img" className={classes} aria-labelledby={a11yTitleId}><title id={a11yTitleId}>{a11yTitle}</title><g><path d="M12,0C5.4,0,0,5.4,0,12s5.4,12,12,12s12-5.4,12-12S18.6,0,12,0z M12,22C6.5,22,2,17.5,2,12
    S6.5,2,12,2s10,4.5,10,10S17.5,22,12,22z"/><polygon points="9,16 17,12 9,8"/></g></svg>;
  }
};

Icon.propTypes = {
  a11yTitle: PropTypes.string,
  a11yTitleId: PropTypes.string,
  colorIndex: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large', 'xlarge', 'huge'])
};

Icon.defaultProps = {
  a11yTitleId: 'watch-title'
};

Icon.icon = true;

Icon.displayName = 'Watch';
<commit_msg>fixing max-len issue with Watch.js icon fallback<commit_after>// (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import FormattedMessage from './FormattedMessage';
import CSSClassnames from '../utils/CSSClassnames';

const CLASS_ROOT = CSSClassnames.CONTROL_ICON;
const COLOR_INDEX = CSSClassnames.COLOR_INDEX;

export default class Icon extends Component {
  render () {
    const { a11yTitleId, className, colorIndex } = this.props;
    let { a11yTitle, size } = this.props;

    const classes = classnames(
      CLASS_ROOT,
      `${CLASS_ROOT}-watch`,
      className,
      {
        [`${CLASS_ROOT}--${size}`]: size,
        [`${COLOR_INDEX}-${colorIndex}`]: colorIndex
      }
    );

    a11yTitle = a11yTitle ||
      <FormattedMessage id="watch" defaultMessage="watch" />;

    return (
      <svg version="1.1" viewBox="0 0 24 24" width="24px"
        height="24px" role="img" className={classes}
        aria-labelledby={a11yTitleId}>
        <title id={a11yTitleId}>{a11yTitle}</title>
        <g>
          <path d="M12,0C5.4,0,0,5.4,0,12s5.4,12,12,12s12-5.4,\
            12-12S18.6,0,12,0z M12,22C6.5,22,2,17.5,2,12
\
            S6.5,2,12,2s10,4.5,10,10S17.5,22,12,22z"/>
          <polygon points="9,16 17,12 9,8"/>
        </g>
      </svg>
    );
  }
};

Icon.propTypes = {
  a11yTitle: PropTypes.string,
  a11yTitleId: PropTypes.string,
  colorIndex: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large', 'xlarge', 'huge'])
};

Icon.defaultProps = {
  a11yTitleId: 'watch-title'
};

Icon.icon = true;

Icon.displayName = 'Watch';
<|endoftext|>


<commit_before>import { combineReducers } from "redux";
import {
  REQUEST_ISSUES,
  RECEIVE_ISSUES,
  SELECT_ISSUE,
  REQUEST_ISSUE,
  RECEIVE_ISSUE,
  INVALIDATE_ISSUE,
  SET_COORDINATES,
  LOAD_COMMENTS,
  POST_COMMENT,
  RECEIVE_COMMENTS,
  ADD_ISSUES_FILTER,
  REMOVE_ISSUES_FILTER,
  RESET_ISSUES_FILTER,
  FETCH_CURRENT_USER_DATA,
  RECEIVE_USER_INFORMATION,
  RECEIVE_ORGANISATIONS,
  RECEIVE_ORGANISATION_DETAILS,
  SET_ISSUE_MAP_BOUNDS,
  SET_ISSUE_DETAIL_ZOOM_LEVEL,
  SET_ORG_MAP_BOUNDS,
  SET_ORG_DETAIL_ZOOM_LEVEL,
  SET_GEOLOCATION
} from "./actions";

import { defaultMapBounds } from "./globals";
import uniq from "lodash/uniq";
import flattenDeep from "lodash/flattenDeep";
import { getBoundsFromPoints } from "./components/map/utils";

function issues(state = [], action) {
  switch (action.type) {
    case RECEIVE_ISSUES:
      return action.issues.map(issue => {
        return {
          ...issue,
          position: [...issue.location.coordinates].reverse()
        };
      });
    default:
      return state;
  }
}

function selectedIssue(state = null, action) {
  switch (action.type) {
    case SELECT_ISSUE:
      return action.issue_id;
    default:
      return state;
  }
}

function issueDetail(
  state = {
    isLoading: true,
    didInvalidate: false
  },
  action
) {
  switch (action.type) {
    case SELECT_ISSUE:
      return {
        ...state,
        isLoading: true,
        didInvalidate: false
      };
    case REQUEST_ISSUE:
      return {
        ...state,
        isLoading: true,
        didInvalidate: false
      };
    case RECEIVE_ISSUE:
      return {
        ...state,
        isLoading: false,
        didInvalidate: false,
        ...action.payload,
        position: [...action.payload.location.coordinates].reverse()
      };
    case INVALIDATE_ISSUE:
      return {
        ...state,
        didInvalidate: true
      };
    default:
      return state;
  }
}

function issueDetails(state = {}, action) {
  switch (action.type) {
    case SELECT_ISSUE:
    case REQUEST_ISSUE:
    case RECEIVE_ISSUE:
    case INVALIDATE_ISSUE:
      return {
        ...state,
        [action.issue_id]: issueDetail(state[action.issue_id], action)
      };
    default:
      return state;
  }
}

function coordinates(state = null, action) {
  switch (action.type) {
    case SET_COORDINATES:
      return action.latLng;
    default:
      return state;
  }
}

function issueComments(
  state = {
    isLoading: true,
    comments: []
  },
  action
) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      return {
        isLoading: false,
        comments: action.payload.comments || []
      };
    default:
      return state;
  }
}

function commentsByIssueID(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      return {
        ...state,
        [action.issue_id]: issueComments(state[action.issue_id], action)
      };
    default:
      return state;
  }
}

function statusChangesByIssueID(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      return {
        ...state,
        [action.issue_id]: action.payload.status_changes
      };
    default:
      return state;
  }
}

function usersByIssueID(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      return {
        ...state,
        [action.issue_id]: action.payload.users
      };
    default:
      return state;
  }
}

function issueFiltersOptions(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ISSUES:
      let issues_with_orgs = action.issues.filter(i => {
        return i.organisation !== null;
      });
      let org_names = uniq(issues_with_orgs.map(i => i.organisation.name));
      return {
        ...state,
        status: uniq(action.issues.map(i => i.status)),
        type: uniq(
          flattenDeep(
            action.issues.map(i =>
              i.issue_types.map(issue_type => issue_type.slug)
            )
          )
        ),
        organisation: org_names
      };
    default:
      return state;
  }
}

function issueFiltersSelections(state, action) {
  let { property, value } = action.filter;
  let cs = [...state[property]];
  let s = {
    ...state
  };
  switch (action.type) {
    case ADD_ISSUES_FILTER:
      if (cs.indexOf(value) === -1) {
        cs.push(value);
      }
      s[property] = cs;
      return s;
    case REMOVE_ISSUES_FILTER:
      s[property] = cs.filter(x => x != value);
      return s;
    default:
      return state;
  }
}

const defaultFilterSelection = {
  status: ["open"],
  type: [],
  organisation: []
};

function issueFilters(
  state = {
    options: {
      status: [],
      type: [],
      organisation: []
    },
    selections: defaultFilterSelection
  },
  action
) {
  switch (action.type) {
    case RECEIVE_ISSUES:
      return {
        ...state,
        options: issueFiltersOptions(state.options, action)
      };
    case ADD_ISSUES_FILTER:
    case REMOVE_ISSUES_FILTER:
      return {
        ...state,
        selections: issueFiltersSelections(state.selections, action)
      };
    case RESET_ISSUES_FILTER:
      return {
        ...state,
        selections: defaultFilterSelection
      };
    default:
      return state;
  }
}

function allUsers(state = [], action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      let users = new Set(action.payload.users.map(u => u.username) || []);
      state.forEach(u => users.add(u));
      return Array.from(users.values());
    default:
      return state;
  }
}

function allOrganisations(state = [], action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      if (action.payload.organisation !== null) {
        let org_slug = action.payload.organisation.short_name;
        return uniq([...state, org_slug]);
      }
      return state;
    case RECEIVE_ISSUES:
      let issues_with_orgs = action.issues.filter(i => i.organisation !== null);
      let org_slugs = issues_with_orgs.map(i => i.organisation.short_name);
      return uniq([...state, ...org_slugs]);
    default:
      return state;
  }
}

function currentUser(
  state = {
    user: {},
    notificationAreas: [],
    canComment: false,
    bbox: defaultMapBounds
  },
  action
) {
  switch (action.type) {
    case FETCH_CURRENT_USER_DATA:
      return {
        ...state,
        ...action.payload
      };
    default:
      return state;
  }
}

function userInformationByUsername(state = {}, action) {
  switch (action.type) {
    case RECEIVE_USER_INFORMATION:
      let key = action.username;
      return {
        ...state,
        [key]: action.payload
      };
    default:
      return state;
  }
}

function loadingStatus(
  state = {
    issues: false
  },
  action
) {
  switch (action.type) {
    case REQUEST_ISSUES:
      return {
        ...state,
        issues: true
      };
    case RECEIVE_ISSUES:
      return {
        ...state,
        issues: false
      };
    default:
      return state;
  }
}

function organisationsByID(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ORGANISATIONS:
      let new_state = {};
      action.payload.forEach(org => {
        let id = parseInt(org.id, 10);
        new_state[id] = org;
      });
      return new_state;
    case RECEIVE_ORGANISATION_DETAILS:
      let id = parseInt(action.payload.id, 10);
      return {
        ...state,
        [id]: {
          ...state[id],
          ...action.payload,
          details_loaded: true
        }
      };
    default:
      return state;
  }
}

function map(
  state = {
    issue_detail: 13,
    org_detail: 13,
    issue_list: defaultMapBounds,
    org_list: defaultMapBounds,
    geolocation: false
  },
  action
) {
  switch (action.type) {
    case RECEIVE_ISSUES:
      // only set bounds for the intial loading of issues
      if (state.issue_list === defaultMapBounds) {
        let locations = action.issues.map(i =>
          [...i.location.coordinates].reverse()
        );
        state = {
          ...state,
          issue_list: getBoundsFromPoints(locations)
        };
      }
      return state;
    case SET_ISSUE_MAP_BOUNDS:
      return {
        ...state,
        issue_list: action.payload
      };
    case SET_ISSUE_DETAIL_ZOOM_LEVEL:
      return {
        ...state,
        issue_detail: action.payload
      };
    case SET_ORG_MAP_BOUNDS:
      return {
        ...state,
        org_list: action.payload
      };
    case SET_ORG_DETAIL_ZOOM_LEVEL:
      return {
        ...state,
        org_detail: action.payload
      };
    case RECEIVE_ORGANISATIONS:
      if (state.org_list === defaultMapBounds) {
        let location_coordinates = action.payload.reduce(
          (coords, organisation) => {
            let location_coords = organisation.locations.map(l =>
              [...l.location.coordinates].reverse()
            );
            return coords.concat(location_coords);
          },
          []
        );
        return {
          ...state,
          org_list: getBoundsFromPoints(location_coordinates)
        };
      }
    case SET_GEOLOCATION:
      return {
        ...state,
        geolocation: action.payload
      };
    default:
      return state;
  }
}

const reducers = {
  redux_issues: issues,
  issueFilters,
  selectedIssue,
  issueDetails,
  coordinates,
  commentsByIssueID,
  statusChangesByIssueID,
  usersByIssueID,
  allUsers,
  allOrganisations,
  currentUser,
  userInformationByUsername,
  loadingStatus,
  organisationsByID,
  map
};

export default reducers;
<commit_msg>in progress now in default filters<commit_after>import { combineReducers } from "redux";
import {
  REQUEST_ISSUES,
  RECEIVE_ISSUES,
  SELECT_ISSUE,
  REQUEST_ISSUE,
  RECEIVE_ISSUE,
  INVALIDATE_ISSUE,
  SET_COORDINATES,
  LOAD_COMMENTS,
  POST_COMMENT,
  RECEIVE_COMMENTS,
  ADD_ISSUES_FILTER,
  REMOVE_ISSUES_FILTER,
  RESET_ISSUES_FILTER,
  FETCH_CURRENT_USER_DATA,
  RECEIVE_USER_INFORMATION,
  RECEIVE_ORGANISATIONS,
  RECEIVE_ORGANISATION_DETAILS,
  SET_ISSUE_MAP_BOUNDS,
  SET_ISSUE_DETAIL_ZOOM_LEVEL,
  SET_ORG_MAP_BOUNDS,
  SET_ORG_DETAIL_ZOOM_LEVEL,
  SET_GEOLOCATION
} from "./actions";

import { defaultMapBounds } from "./globals";
import uniq from "lodash/uniq";
import flattenDeep from "lodash/flattenDeep";
import { getBoundsFromPoints } from "./components/map/utils";

function issues(state = [], action) {
  switch (action.type) {
    case RECEIVE_ISSUES:
      return action.issues.map(issue => {
        return {
          ...issue,
          position: [...issue.location.coordinates].reverse()
        };
      });
    default:
      return state;
  }
}

function selectedIssue(state = null, action) {
  switch (action.type) {
    case SELECT_ISSUE:
      return action.issue_id;
    default:
      return state;
  }
}

function issueDetail(
  state = {
    isLoading: true,
    didInvalidate: false
  },
  action
) {
  switch (action.type) {
    case SELECT_ISSUE:
      return {
        ...state,
        isLoading: true,
        didInvalidate: false
      };
    case REQUEST_ISSUE:
      return {
        ...state,
        isLoading: true,
        didInvalidate: false
      };
    case RECEIVE_ISSUE:
      return {
        ...state,
        isLoading: false,
        didInvalidate: false,
        ...action.payload,
        position: [...action.payload.location.coordinates].reverse()
      };
    case INVALIDATE_ISSUE:
      return {
        ...state,
        didInvalidate: true
      };
    default:
      return state;
  }
}

function issueDetails(state = {}, action) {
  switch (action.type) {
    case SELECT_ISSUE:
    case REQUEST_ISSUE:
    case RECEIVE_ISSUE:
    case INVALIDATE_ISSUE:
      return {
        ...state,
        [action.issue_id]: issueDetail(state[action.issue_id], action)
      };
    default:
      return state;
  }
}

function coordinates(state = null, action) {
  switch (action.type) {
    case SET_COORDINATES:
      return action.latLng;
    default:
      return state;
  }
}

function issueComments(
  state = {
    isLoading: true,
    comments: []
  },
  action
) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      return {
        isLoading: false,
        comments: action.payload.comments || []
      };
    default:
      return state;
  }
}

function commentsByIssueID(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      return {
        ...state,
        [action.issue_id]: issueComments(state[action.issue_id], action)
      };
    default:
      return state;
  }
}

function statusChangesByIssueID(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      return {
        ...state,
        [action.issue_id]: action.payload.status_changes
      };
    default:
      return state;
  }
}

function usersByIssueID(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      return {
        ...state,
        [action.issue_id]: action.payload.users
      };
    default:
      return state;
  }
}

function issueFiltersOptions(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ISSUES:
      let issues_with_orgs = action.issues.filter(i => {
        return i.organisation !== null;
      });
      let org_names = uniq(issues_with_orgs.map(i => i.organisation.name));
      return {
        ...state,
        status: uniq(action.issues.map(i => i.status)),
        type: uniq(
          flattenDeep(
            action.issues.map(i =>
              i.issue_types.map(issue_type => issue_type.slug)
            )
          )
        ),
        organisation: org_names
      };
    default:
      return state;
  }
}

function issueFiltersSelections(state, action) {
  let { property, value } = action.filter;
  let cs = [...state[property]];
  let s = {
    ...state
  };
  switch (action.type) {
    case ADD_ISSUES_FILTER:
      if (cs.indexOf(value) === -1) {
        cs.push(value);
      }
      s[property] = cs;
      return s;
    case REMOVE_ISSUES_FILTER:
      s[property] = cs.filter(x => x != value);
      return s;
    default:
      return state;
  }
}

const defaultFilterSelection = {
  status: ["open", "in progress"],
  type: [],
  organisation: []
};

function issueFilters(
  state = {
    options: {
      status: [],
      type: [],
      organisation: []
    },
    selections: defaultFilterSelection
  },
  action
) {
  switch (action.type) {
    case RECEIVE_ISSUES:
      return {
        ...state,
        options: issueFiltersOptions(state.options, action)
      };
    case ADD_ISSUES_FILTER:
    case REMOVE_ISSUES_FILTER:
      return {
        ...state,
        selections: issueFiltersSelections(state.selections, action)
      };
    case RESET_ISSUES_FILTER:
      return {
        ...state,
        selections: defaultFilterSelection
      };
    default:
      return state;
  }
}

function allUsers(state = [], action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      let users = new Set(action.payload.users.map(u => u.username) || []);
      state.forEach(u => users.add(u));
      return Array.from(users.values());
    default:
      return state;
  }
}

function allOrganisations(state = [], action) {
  switch (action.type) {
    case RECEIVE_ISSUE:
      if (action.payload.organisation !== null) {
        let org_slug = action.payload.organisation.short_name;
        return uniq([...state, org_slug]);
      }
      return state;
    case RECEIVE_ISSUES:
      let issues_with_orgs = action.issues.filter(i => i.organisation !== null);
      let org_slugs = issues_with_orgs.map(i => i.organisation.short_name);
      return uniq([...state, ...org_slugs]);
    default:
      return state;
  }
}

function currentUser(
  state = {
    user: {},
    notificationAreas: [],
    canComment: false,
    bbox: defaultMapBounds
  },
  action
) {
  switch (action.type) {
    case FETCH_CURRENT_USER_DATA:
      return {
        ...state,
        ...action.payload
      };
    default:
      return state;
  }
}

function userInformationByUsername(state = {}, action) {
  switch (action.type) {
    case RECEIVE_USER_INFORMATION:
      let key = action.username;
      return {
        ...state,
        [key]: action.payload
      };
    default:
      return state;
  }
}

function loadingStatus(
  state = {
    issues: false
  },
  action
) {
  switch (action.type) {
    case REQUEST_ISSUES:
      return {
        ...state,
        issues: true
      };
    case RECEIVE_ISSUES:
      return {
        ...state,
        issues: false
      };
    default:
      return state;
  }
}

function organisationsByID(state = {}, action) {
  switch (action.type) {
    case RECEIVE_ORGANISATIONS:
      let new_state = {};
      action.payload.forEach(org => {
        let id = parseInt(org.id, 10);
        new_state[id] = org;
      });
      return new_state;
    case RECEIVE_ORGANISATION_DETAILS:
      let id = parseInt(action.payload.id, 10);
      return {
        ...state,
        [id]: {
          ...state[id],
          ...action.payload,
          details_loaded: true
        }
      };
    default:
      return state;
  }
}

function map(
  state = {
    issue_detail: 13,
    org_detail: 13,
    issue_list: defaultMapBounds,
    org_list: defaultMapBounds,
    geolocation: false
  },
  action
) {
  switch (action.type) {
    case RECEIVE_ISSUES:
      // only set bounds for the intial loading of issues
      if (state.issue_list === defaultMapBounds) {
        let locations = action.issues.map(i =>
          [...i.location.coordinates].reverse()
        );
        state = {
          ...state,
          issue_list: getBoundsFromPoints(locations)
        };
      }
      return state;
    case SET_ISSUE_MAP_BOUNDS:
      return {
        ...state,
        issue_list: action.payload
      };
    case SET_ISSUE_DETAIL_ZOOM_LEVEL:
      return {
        ...state,
        issue_detail: action.payload
      };
    case SET_ORG_MAP_BOUNDS:
      return {
        ...state,
        org_list: action.payload
      };
    case SET_ORG_DETAIL_ZOOM_LEVEL:
      return {
        ...state,
        org_detail: action.payload
      };
    case RECEIVE_ORGANISATIONS:
      if (state.org_list === defaultMapBounds) {
        let location_coordinates = action.payload.reduce(
          (coords, organisation) => {
            let location_coords = organisation.locations.map(l =>
              [...l.location.coordinates].reverse()
            );
            return coords.concat(location_coords);
          },
          []
        );
        return {
          ...state,
          org_list: getBoundsFromPoints(location_coordinates)
        };
      }
    case SET_GEOLOCATION:
      return {
        ...state,
        geolocation: action.payload
      };
    default:
      return state;
  }
}

const reducers = {
  redux_issues: issues,
  issueFilters,
  selectedIssue,
  issueDetails,
  coordinates,
  commentsByIssueID,
  statusChangesByIssueID,
  usersByIssueID,
  allUsers,
  allOrganisations,
  currentUser,
  userInformationByUsername,
  loadingStatus,
  organisationsByID,
  map
};

export default reducers;
<|endoftext|>