<commit_before>var Store = require('../store');

Store.addExceptionType(function UnknownAttributeTypeError(type){
  Error.apply(this);
  this.message = 'Unknown attribute type "' + type + '"';
});

/*
 * DEFINITION
 */
exports.definition = {

  mixinCallback: function(){
    this.attributes = {};
  },

  /**
   * Add a new attribute to your Model
   *
   * @area Definition
   * @method attribute
   * @param {string} name - The attribute name
   * @param {string} type - The attribute type (e.g. `text`, `integer`, or sql language specific. e.g. `blob`)
   * @param {object} options - Optional options
   *
   * @options
   * @param {boolean} writable - make it writeable (default: true)
   * @param {boolean} readable - make it readable (default: true)
   *
   * @return {Definition}
   */
  attribute: function(name, type, options){
    options = options || {};
    type = type || String;

    if(typeof type == 'string'){
      type = type.toLowerCase();
    }

    var field_type = this.store.getType(type);
    if(!field_type){
      this.handleException(new Store.UnknownAttributeTypeError(type));
    }

    options.type = field_type;
    options.writable = options.writable === undefined ? true : (options.writable ? true : false);
    options.readable = options.readable === undefined ? true : (options.readable ? true : false);

    this.attributes[name] = options;

    this.setter(name, function(value){
      this.set(name, value);
    });

    if(options.readable){
      this.getter(name, function(){
        return this.attributes[name];
      });
    }

    return this;
  },

  cast: function(attribute, value, cast_name){
    var cast_name = cast_name || 'input';
    var attr = this.attributes[attribute];
    var cast = attr ? attr.type.cast[cast_name] : null;

    if(attr && cast){
      if(value instanceof Array){
        for(var i = 0; i < value.length; i++){
          value[i] = cast(value[i]);
        }
      }else{
        value = cast(value);
      }

      return value;
    }
    return value;
  },

  getter: function(name, fn){

    this.instanceMethods.__defineGetter__(name, fn);

    return this;
  },

  setter: function(name, fn){

    this.instanceMethods.__defineSetter__(name, fn);

    return this;
  }
};

/*
 * RECORD
 */
exports.record = {
  mixinCallback: function(config){
    this.attributes = {};
    this.changes = {}; // e.g. {login: ['phil', 'philipp']} first value is the original, second is the changed value

    this.set(config || {});
  },

  /**
   * Set one or multiple attributes of a Record.
   *
   * @area Record
   * @method set
   * @param {string} name - The attributes name
   * @param {VALUE} value - The attributes value
   * @param OR
   * @param {object} attributes - Multiple attribute as an object
   *
   * @return {Record}
   */
  set: function(name, value){
    var values = name;
    if(typeof name == 'string'){
      values = {};
      values[name] = value;
    }

    for(var field in this.definition.attributes){
      if(this.definition.attributes.hasOwnProperty(field)){
        var definition = this.definition.attributes[field];
        var value = values[field];

        if(value === undefined && this.attributes[field] === undefined){
          value = definition.default;
        }

        if(value === undefined && this.attributes[field] !== undefined){
          value = this.attributes[field];
        }

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

        //typecasted value
        value = value !== null ? definition.type.cast.input(value) : null;

        if(this.attributes[field] !== value){

          //emit old_value, new_value
          this.definition.emit(this, field + '_changed', this.attributes[field],  value);

          if(definition.writable){
            if(this.changes[field]){
              this.changes[field][1] = value;
            }else{
              this.changes[field] = [this.attributes[field], value];
            }
          }

          this.attributes[field] = value;
        }
      }
    }

    return this;
  },

  /**
   * Get an attributes.
   *
   * @area Record
   * @method get
   * @param {string} name - The attributes name
   *
   * @return {VALUE}
   */
  get: function(name){
    return this.attributes[name];
  },

  /**
   * Returns `true` if there are any changed values in that record
   *
   * @area Record
   * @method hasChanges
   *
   * @return {boolean}
   */
  hasChanges: function(){
    return Object.keys(this.changes).length > 0;
  },

  /**
   * Returns an object with all the changes. One attribute will always include the original and current value
   *
   * @area Record
   * @method getChanges
   *
   * @return {object}
   */
  getChanges: function(){
    return this.changes;
  },

  /**
   * Returns an object with all changed values
   *
   * @area Record
   * @method getChangedValues
   *
   * @return {object}
   */
  getChangedValues: function(){
    var tmp = {};
    for(var name in this.changes){
      if(this.changes.hasOwnProperty(name)){
        tmp[name] = this.changes[name][1];
      }
    }
    return tmp;
  },

  /**
   * Resets all changes to the original values
   *
   * @area Record
   * @method resetChanges
   *
   * @return {Record}
   */
  resetChanges: function(){
    for(var name in this.changes){
      if(this.changes.hasOwnProperty(name)){
        this[name] = this.changes[name][0];
      }
    }

    this.changes = {};
    return this;
  }

};<commit_msg>attributes bugfix<commit_after>var Store = require('../store');

Store.addExceptionType(function UnknownAttributeTypeError(type){
  Error.apply(this);
  this.message = 'Unknown attribute type "' + type + '"';
});

/*
 * DEFINITION
 */
exports.definition = {

  mixinCallback: function(){
    this.attributes = {};
  },

  /**
   * Add a new attribute to your Model
   *
   * @area Definition
   * @method attribute
   * @param {string} name - The attribute name
   * @param {string} type - The attribute type (e.g. `text`, `integer`, or sql language specific. e.g. `blob`)
   * @param {object} options - Optional options
   *
   * @options
   * @param {boolean} writable - make it writeable (default: true)
   * @param {boolean} readable - make it readable (default: true)
   *
   * @return {Definition}
   */
  attribute: function(name, type, options){
    options = options || {};
    type = type || String;

    if(typeof type == 'string'){
      type = type.toLowerCase();
    }

    var field_type = this.store.getType(type);
    if(!field_type){
      this.handleException(new Store.UnknownAttributeTypeError(type));
    }

    options.type = field_type;
    options.writable = options.writable === undefined ? true : (options.writable ? true : false);
    options.readable = options.readable === undefined ? true : (options.readable ? true : false);

    this.attributes[name] = options;

    this.setter(name, function(value){
      this.set(name, value);
    });

    if(options.readable){
      this.getter(name, function(){
        return this.attributes[name];
      });
    }

    return this;
  },

  cast: function(attribute, value, cast_name){
    var cast_name = cast_name || 'input';
    var attr = this.attributes[attribute];
    var cast = attr ? attr.type.cast[cast_name] : null;

    if(attr && cast){
      if(value instanceof Array){
        for(var i = 0; i < value.length; i++){
          value[i] = cast(value[i]);
        }
      }else{
        value = cast(value);
      }

      return value;
    }
    return value;
  },

  getter: function(name, fn){

    this.instanceMethods.__defineGetter__(name, fn);

    return this;
  },

  setter: function(name, fn){

    this.instanceMethods.__defineSetter__(name, fn);

    return this;
  }
};

/*
 * RECORD
 */
exports.record = {
  mixinCallback: function(config){
    this.attributes = {};
    this.changes = {}; // e.g. {login: ['phil', 'philipp']} first value is the original, second is the changed value

    this.set(config || {});
  },

  /**
   * Set one or multiple attributes of a Record.
   *
   * @area Record
   * @method set
   * @param {string} name - The attributes name
   * @param {VALUE} value - The attributes value
   * @param OR
   * @param {object} attributes - Multiple attribute as an object
   *
   * @return {Record}
   */
  set: function(name, value){
    var values = name;
    if(typeof name == 'string'){
      values = {};
      values[name] = value;
    }

    for(var field in this.definition.attributes){
      if(this.definition.attributes.hasOwnProperty(field)){
        var definition = this.definition.attributes[field];
        var value = values[field];

        if(value === undefined && this.attributes[field] === undefined){
          value = definition.default;
        }

        if(value === undefined && this.attributes[field] !== undefined){
          value = this.attributes[field];
        }

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

        //typecasted value
        value = value !== null ? definition.type.cast.input(value) : null;

        if(this.attributes[field] !== value){

          //emit old_value, new_value
          this.definition.emit(this, field + '_changed', this.attributes[field],  value);

          if(definition.writable && !(value === null && this.attributes[field] === undefined)){
            if(this.changes[field]){
              this.changes[field][1] = value;
            }else{
              this.changes[field] = [this.attributes[field], value];
            }
          }

          this.attributes[field] = value;
        }
      }
    }

    return this;
  },

  /**
   * Get an attributes.
   *
   * @area Record
   * @method get
   * @param {string} name - The attributes name
   *
   * @return {VALUE}
   */
  get: function(name){
    return this.attributes[name];
  },

  /**
   * Returns `true` if there are any changed values in that record
   *
   * @area Record
   * @method hasChanges
   *
   * @return {boolean}
   */
  hasChanges: function(){
    return Object.keys(this.changes).length > 0;
  },

  /**
   * Returns an object with all the changes. One attribute will always include the original and current value
   *
   * @area Record
   * @method getChanges
   *
   * @return {object}
   */
  getChanges: function(){
    return this.changes;
  },

  /**
   * Returns an object with all changed values
   *
   * @area Record
   * @method getChangedValues
   *
   * @return {object}
   */
  getChangedValues: function(){
    var tmp = {};
    for(var name in this.changes){
      if(this.changes.hasOwnProperty(name)){
        tmp[name] = this.changes[name][1];
      }
    }
    return tmp;
  },

  /**
   * Resets all changes to the original values
   *
   * @area Record
   * @method resetChanges
   *
   * @return {Record}
   */
  resetChanges: function(){
    for(var name in this.changes){
      if(this.changes.hasOwnProperty(name)){
        this[name] = this.changes[name][0];
      }
    }

    this.changes = {};
    return this;
  }

};<|endoftext|>


<commit_before>module.exports = {
    isDev: function isDev ( user ) {
        if ( !user[ this.type ] ) {
            return false;
        }

        if ( this.list.includes( user[ this.type ] ) ) {
            return false;
        }

        return true;
    },
};
<commit_msg>Flairs should really be lowercase<commit_after>module.exports = {
    isDev: function isDev ( user ) {
        if ( !user[ this.type ] ) {
            return false;
        }

        if ( this.list.includes( user[ this.type ].toLowerCase() ) ) {
            return false;
        }

        return true;
    },
};
<|endoftext|>


<commit_before>'use strict';

var merge = require('lodash').merge;
var data = require('gulp-data');
var gulpif = require('gulp-if');
var plumber = require('gulp-plumber');
var frontMatter = require('front-matter');
var fs = require('fs');
var handlebars = require('gulp-compile-handlebars');
var path = require('path');
var rename = require('gulp-rename');

var defaults = {
  sharedData: {},
  dest: './dist',
  layoutDir: './src/layouts',
  name: 'html',
  plugins: {
    handlebars: {
      ignorePartials: true,
      batch: ['./src/partials']
    }
  },
  src: './src/*.hbs',
  templateExt: 'hbs',
  plumb: true
};

module.exports = function(gulp, options) {
  var opts = merge({}, defaults, options);

  var sharedData = opts.sharedData;

  var dest = opts.dest;

  var layoutDir = opts.layoutDir;

  var name = opts.name;

  var plugins = opts.plugins;

  var src = opts.src;

  var templateExt = opts.templateExt;

  var plumb = opts.plumb;
  // Object destructuring is being difficult right now
  // var { sharedData, dest, layoutDir, plugins, src, templateExt } = opts;

  /* Helper to extract meta data embedded in the file contents. */
  function parseContent(file) {
    return frontMatter(String(file.contents));
  }

  /* Helper to resolve template paths. */
  function getLayoutPath(name) {
    return path.resolve(
      process.cwd(),
      `${layoutDir}/`,
      `${name}.${templateExt}`
    );
  }

  gulp.task(name, function() {
    /* Helper to abstract handlebars sub-task. */
    var compileHtml = function() {
      return handlebars(sharedData, plugins.handlebars);
    };

    /* Helper to abstract FrontMatter sub-task. */
    var parseData = function(file) {
      var content = parseContent(file);
      file.contents = Buffer.from(content.body);
      return content.attributes;
    };

    /* Helper to abstract template replacement sub-task. */
    var wrapHtml = function(file, name) {
      var html = String(file.contents);

      var path = getLayoutPath(name);

      file.contents = fs.readFileSync(path);
      return { content: html };
    };

    return gulp
      .src(src)
      .pipe(gulpif(plumb, plumber()))
      .pipe(data(file => parseData(file)))
      .pipe(compileHtml())
      .pipe(data(file => wrapHtml(file, file.data.layout)))
      .pipe(compileHtml())
      .pipe(rename(path => (path.extname = '.html')))
      .pipe(gulp.dest(dest));
  });
};
<commit_msg>Remove extra lines from eslint splitting var declaration<commit_after>'use strict';

var merge = require('lodash').merge;
var data = require('gulp-data');
var gulpif = require('gulp-if');
var plumber = require('gulp-plumber');
var frontMatter = require('front-matter');
var fs = require('fs');
var handlebars = require('gulp-compile-handlebars');
var path = require('path');
var rename = require('gulp-rename');

var defaults = {
  sharedData: {},
  dest: './dist',
  layoutDir: './src/layouts',
  name: 'html',
  plugins: {
    handlebars: {
      ignorePartials: true,
      batch: ['./src/partials']
    }
  },
  src: './src/*.hbs',
  templateExt: 'hbs',
  plumb: true
};

module.exports = function(gulp, options) {
  var opts = merge({}, defaults, options);
  var sharedData = opts.sharedData;
  var dest = opts.dest;
  var layoutDir = opts.layoutDir;
  var name = opts.name;
  var plugins = opts.plugins;
  var src = opts.src;
  var templateExt = opts.templateExt;
  var plumb = opts.plumb;
  // Object destructuring is being difficult right now
  // var { sharedData, dest, layoutDir, plugins, src, templateExt } = opts;

  /* Helper to extract meta data embedded in the file contents. */
  function parseContent(file) {
    return frontMatter(String(file.contents));
  }

  /* Helper to resolve template paths. */
  function getLayoutPath(name) {
    return path.resolve(
      process.cwd(),
      `${layoutDir}/`,
      `${name}.${templateExt}`
    );
  }

  gulp.task(name, function() {
    /* Helper to abstract handlebars sub-task. */
    var compileHtml = function() {
      return handlebars(sharedData, plugins.handlebars);
    };

    /* Helper to abstract FrontMatter sub-task. */
    var parseData = function(file) {
      var content = parseContent(file);
      file.contents = Buffer.from(content.body);
      return content.attributes;
    };

    /* Helper to abstract template replacement sub-task. */
    var wrapHtml = function(file, name) {
      var html = String(file.contents);

      var path = getLayoutPath(name);

      file.contents = fs.readFileSync(path);
      return { content: html };
    };

    return gulp
      .src(src)
      .pipe(gulpif(plumb, plumber()))
      .pipe(data(file => parseData(file)))
      .pipe(compileHtml())
      .pipe(data(file => wrapHtml(file, file.data.layout)))
      .pipe(compileHtml())
      .pipe(rename(path => (path.extname = '.html')))
      .pipe(gulp.dest(dest));
  });
};
<|endoftext|>


<commit_before>angular.module('mwUI.UiComponents')
//TODO rename
/**
 * @ngdoc directive
 * @name mwUI.UiComponents.directive:mwTabs
 *
 * @description Shows a tab view. Requires that you transclude mw-tabs-pane. They define the title, icon, etc of the tab
 * When setting removeInactiveContent so true it will remove the tab content of inactive tabs from the dom. This is
 * recommended for tab content that has a complex logic
 *
 * @param {justified} boolean defines the arrangement of the tabs. When set to true they fill available with. Default false
 * @param {activePaneNumber} number defines the tab that should be selected. Starts with 1
 * @param {activePaneId} string alternative to activePaneNumber defines the active tab by its id. Requires that you set an id on mw-tabs-pane
 * @param {tabChanged} function the callback that shall be executed when the tab is changed. Will pass the activePaneNumber, newPane scope, previous pane scope as params
 * @param {removeInactiveContent} boolean defines wether inactive pane content should be removed from dom or just be set to hidden. Default false
 *
 * @example ```html
 * <!-- change callback example -->
 * <div mw-tabs
 *      active-pane-number="myCtrl.activePane"
 *      tab-changed="myCtrl.tabChanged"
 *      remove-inactive-content="true">
 *   <div mw-tabs-pane="{{'mytitle'| i18n}}">
 *     Tab Content 1
 *   </div>
 *   <div mw-tabs-pane="{{'mytitle_2'| i18n}}">
 *     Tab Content 2
 *   </div>
 * </div>
 * ```
 */
  .directive('mwTabs', function ($rootScope) {
    return {
      transclude: true,
      scope: {
        justified: '=?',
        activePaneNumber: '=?',
        activePaneId: '@?',
        tabChanged: '=?',
        removeInactiveContent: '=?'
      },
      templateUrl: 'uikit/mw-ui-components/directives/templates/mw_tab_bar.html',
      controller: function ($scope) {
        // The panes are populated by the mw-tabs-pane directive that is calling `registerPane`of this controller
        // It is defined as a scope attribute so it is available in this html template
        var activePaneIndex;

        $scope.panes = [];

        var setInitialSelection = function () {
          activePaneIndex = null;

          // In case that no active pane is defined by setting activePaneNumber or Id the first pane will be selected
          if (angular.isUndefined($scope.activePaneNumber) && angular.isUndefined($scope.activePaneId) && $scope.panes.length > 0) {
            $scope.select($scope.panes[0]);

          // When a pane number is defined the pane with the index of activePaneNumber + 1 will be selected
          // So when the second tab shall be selected set activePaneNumber to 2
          } else if (angular.isDefined($scope.activePaneNumber)) {
            $scope.selectTabByNumber($scope.activePaneNumber);

          // When a pane id is defined the pane where the pane id equals the activePaneId will be selected
          // Make sure to set an id on the mw-tab-pane
          } else if (angular.isDefined($scope.activePaneId)) {
            $scope.selectTabById($scope.activePaneId);
          }
        };

        var throttledSetInitialSelection = _.debounce(setInitialSelection, 100);

        $scope.getActivePane = function () {
          var activePane;
          $scope.panes.every(function (pane) {
            if (pane.isSelected()) {
              activePane = pane;
              return false;
            }
            return true;
          });
          return activePane;
        };

        $scope.select = function (newPane) {
          var newPaneIndex = _.indexOf($scope.panes, newPane);

          if (newPane && newPaneIndex !== -1 && newPaneIndex !== activePaneIndex) {
            var previousSelectedPane = $scope.getActivePane();
            if (previousSelectedPane) {
              previousSelectedPane.deselect();
            }

            newPane.select();
            activePaneIndex = newPaneIndex;

            if ($scope.tabChanged && typeof $scope.tabChanged === 'function') {
              $scope.tabChanged(activePaneIndex + 1, newPane, previousSelectedPane);
              $rootScope.$emit('$mwTabChange');
            }
          }
        };

        $scope.selectTabByNumber = function (number) {
          if (number > 0 && number <= $scope.panes.length) {
            $scope.select($scope.panes[number - 1]);
          }
        };

        $scope.selectTabById = function (id) {
          $scope.panes.every(function (pane) {
            if (pane.getId() === id) {
              $scope.select(pane);
              return false;
            }
            return true;
          });
        };

        $scope.getActivePaneIndex = function () {
          if (activePaneIndex) {
            return activePaneIndex;
          } else {
            return 0;
          }
        };

        $scope.canShowPaneContent = function () {
          var activePane = $scope.panes[activePaneIndex];
          return (activePane && activePane.isInitialised);
        };

        // add a change listener on the pane
        $scope.$watch('activePaneNumber', function (_new, _old) {
          if (_new && _new !== _old) {
            $scope.selectTabByNumber(_new);
          }
        });

        $scope.$watch('activePaneId', function (_newId) {
          if (_newId) {
            $scope.selectTabById(_newId);
          }
        });

        this.registerPane = function (pane) {
          $scope.panes.push(pane);
          throttledSetInitialSelection();
        };

        this.unRegisterPane = function (pane) {
          if (pane) {
            var indexOfExistingPane = _.indexOf($scope.panes, pane);
            if (indexOfExistingPane !== -1) {
              $scope.panes.splice(indexOfExistingPane, 1);
            }
            throttledSetInitialSelection();
          }
        };

        this.canRemoveInactiveContent = function(){
          return $scope.removeInactiveContent;
        };
      }
    };
  });<commit_msg>Set debounce time to 10<commit_after>angular.module('mwUI.UiComponents')
//TODO rename
/**
 * @ngdoc directive
 * @name mwUI.UiComponents.directive:mwTabs
 *
 * @description Shows a tab view. Requires that you transclude mw-tabs-pane. They define the title, icon, etc of the tab
 * When setting removeInactiveContent so true it will remove the tab content of inactive tabs from the dom. This is
 * recommended for tab content that has a complex logic
 *
 * @param {justified} boolean defines the arrangement of the tabs. When set to true they fill available with. Default false
 * @param {activePaneNumber} number defines the tab that should be selected. Starts with 1
 * @param {activePaneId} string alternative to activePaneNumber defines the active tab by its id. Requires that you set an id on mw-tabs-pane
 * @param {tabChanged} function the callback that shall be executed when the tab is changed. Will pass the activePaneNumber, newPane scope, previous pane scope as params
 * @param {removeInactiveContent} boolean defines wether inactive pane content should be removed from dom or just be set to hidden. Default false
 *
 * @example ```html
 * <!-- change callback example -->
 * <div mw-tabs
 *      active-pane-number="myCtrl.activePane"
 *      tab-changed="myCtrl.tabChanged"
 *      remove-inactive-content="true">
 *   <div mw-tabs-pane="{{'mytitle'| i18n}}">
 *     Tab Content 1
 *   </div>
 *   <div mw-tabs-pane="{{'mytitle_2'| i18n}}">
 *     Tab Content 2
 *   </div>
 * </div>
 * ```
 */
  .directive('mwTabs', function ($rootScope) {
    return {
      transclude: true,
      scope: {
        justified: '=?',
        activePaneNumber: '=?',
        activePaneId: '@?',
        tabChanged: '=?',
        removeInactiveContent: '=?'
      },
      templateUrl: 'uikit/mw-ui-components/directives/templates/mw_tab_bar.html',
      controller: function ($scope) {
        // The panes are populated by the mw-tabs-pane directive that is calling `registerPane`of this controller
        // It is defined as a scope attribute so it is available in this html template
        var activePaneIndex;

        $scope.panes = [];

        var setInitialSelection = function () {
          activePaneIndex = null;

          // In case that no active pane is defined by setting activePaneNumber or Id the first pane will be selected
          if (angular.isUndefined($scope.activePaneNumber) && angular.isUndefined($scope.activePaneId) && $scope.panes.length > 0) {
            $scope.select($scope.panes[0]);

          // When a pane number is defined the pane with the index of activePaneNumber + 1 will be selected
          // So when the second tab shall be selected set activePaneNumber to 2
          } else if (angular.isDefined($scope.activePaneNumber)) {
            $scope.selectTabByNumber($scope.activePaneNumber);

          // When a pane id is defined the pane where the pane id equals the activePaneId will be selected
          // Make sure to set an id on the mw-tab-pane
          } else if (angular.isDefined($scope.activePaneId)) {
            $scope.selectTabById($scope.activePaneId);
          }
        };

        var throttledSetInitialSelection = _.debounce(setInitialSelection, 10);

        $scope.getActivePane = function () {
          var activePane;
          $scope.panes.every(function (pane) {
            if (pane.isSelected()) {
              activePane = pane;
              return false;
            }
            return true;
          });
          return activePane;
        };

        $scope.select = function (newPane) {
          var newPaneIndex = _.indexOf($scope.panes, newPane);

          if (newPane && newPaneIndex !== -1 && newPaneIndex !== activePaneIndex) {
            var previousSelectedPane = $scope.getActivePane();
            if (previousSelectedPane) {
              previousSelectedPane.deselect();
            }

            newPane.select();
            activePaneIndex = newPaneIndex;

            if ($scope.tabChanged && typeof $scope.tabChanged === 'function') {
              $scope.tabChanged(activePaneIndex + 1, newPane, previousSelectedPane);
              $rootScope.$emit('$mwTabChange');
            }
          }
        };

        $scope.selectTabByNumber = function (number) {
          if (number > 0 && number <= $scope.panes.length) {
            $scope.select($scope.panes[number - 1]);
          }
        };

        $scope.selectTabById = function (id) {
          $scope.panes.every(function (pane) {
            if (pane.getId() === id) {
              $scope.select(pane);
              return false;
            }
            return true;
          });
        };

        $scope.getActivePaneIndex = function () {
          if (activePaneIndex) {
            return activePaneIndex;
          } else {
            return 0;
          }
        };

        $scope.canShowPaneContent = function () {
          var activePane = $scope.panes[activePaneIndex];
          return (activePane && activePane.isInitialised);
        };

        // add a change listener on the pane
        $scope.$watch('activePaneNumber', function (_new, _old) {
          if (_new && _new !== _old) {
            $scope.selectTabByNumber(_new);
          }
        });

        $scope.$watch('activePaneId', function (_newId) {
          if (_newId) {
            $scope.selectTabById(_newId);
          }
        });

        this.registerPane = function (pane) {
          $scope.panes.push(pane);
          throttledSetInitialSelection();
        };

        this.unRegisterPane = function (pane) {
          if (pane) {
            var indexOfExistingPane = _.indexOf($scope.panes, pane);
            if (indexOfExistingPane !== -1) {
              $scope.panes.splice(indexOfExistingPane, 1);
            }
            throttledSetInitialSelection();
          }
        };

        this.canRemoveInactiveContent = function(){
          return $scope.removeInactiveContent;
        };
      }
    };
  });<|endoftext|>


<commit_before>import axios from 'axios'
import * as log from 'electron-log'
import R from 'ramda'

export class WowsApi {
  constructor (key, url, shipdb) {
    this.api = axios.create({
      baseURL: url,
      timeout: 20000,
      params: {
        application_id: key
      }
    })

    const responseHandler = (response) => {
      if (response.data.status !== 'ok' || R.path(['data', 'error'], response)) {
        return Promise.reject(R.path(['data', 'error'], response))
      } else {
        return response
      }
    }

    const errorHandler = (error) => {
      return Promise.reject(error)
    }

    this.api.interceptors.response.use(responseHandler, errorHandler)

    this.shipdb = shipdb
    this.key = key
    this.url = url
  }

  clearCache () {
    this.shipdb.clear()
  }

  findPlayer (playerName) {
    return new Promise((resolve, reject) => {
      // Find the player's account ID. We use Wargaming's
      // API to search by the player's name. The API returns a list of
      // players whose names match our search (i.e. searching for 'Alfred'
      // returns 'Alfred1', 'Alfred_accurate', 'AlfredAlfred' an so on), so
      // we have to search the response list ourselves for the right player
      this.api.get(`/wows/account/list/?search=${encodeURIComponent(playerName)}`)
        .then(response => {
          const playerRecord = R.find(R.propEq('nickname', playerName))(response.data.data)
          return playerRecord
            ? resolve({
              // Player found, so return his account ID
              accountId: playerRecord.account_id,
              name: playerRecord.nickname
            })
            : reject(Error('Player not found'))
        })
        .catch(error => {
          console.log(error)
          return Error('Player not found')
        })
    })
  }

  getPlayers (accountIds, matchGroup = 'pvp') {
    if (matchGroup === 'ranked') matchGroup = 'rank_solo'
    else if (matchGroup === 'cooperative') matchGroup = 'pve'
    return new Promise((resolve, reject) => {
      // Our second step is looking up the player's stats using their account IDs.
      const params = {
        account_id: R.map(R.prop('accountId'), accountIds).join(','),
        extra: ''
      }
      if (matchGroup === 'rank_solo') params.extra = 'statistics.rank_solo'
      if (matchGroup === 'pve') params.extra = 'statistics.pve'

      this.api.get('/wows/account/info/', { params: params })
        .then(response => {
          const players = R.mapObjIndexed((playerStats, accountId, obj) => {
            if (playerStats && (!playerStats.statistics || playerStats.hidden_profile || !R.path(['statistics', matchGroup], playerStats))) {
              return {
                name: playerStats.nickname,
                hidden: true
              }
            } else {
              const group = playerStats.statistics[matchGroup]
              return {
                battles: group.battles,
                winrate: (group.wins / group.battles * 100),
                avgExp: (group.xp / group.battles),
                avgDmg: (group.damage_dealt / group.battles),
                kdRatio: (group.frags / (group.battles - group.survived_battles)),
                name: playerStats.nickname
              }
            }
          })(response.data.data)
          return resolve(players)
        })
        .catch(error => {
          return reject(error)
        })
    })
  }

  getPlayerClan (accountId) {
    return new Promise((resolve, reject) => {
      this.api.get('/wows/clans/accountinfo/', { params: { account_id: accountId, extra: 'clan' } })
        .then(response => {
          const clan = R.path(['data', 'data', accountId, 'clan'], response)
          return clan
            ? resolve({
              id: clan.clan_id,
              createdAt: clan.created_at,
              membersCount: clan.members_count,
              name: clan.name,
              tag: clan.tag
            })
            : reject(Error('Player is not in a clan.'))
        })
        .catch(error => {
          console.log(error)
          return reject(error)
        })
    })
  }

  getShipName (shipId) {
    return new Promise((resolve, reject) => {
      if (this.shipdb.hasName(shipId)) {
        let shipName = this.shipdb.getName(shipId)
        log.info(`Cache hit: ${shipId} => ${shipName}`)
        return resolve(shipName)
      } else {
        log.info(`Cache miss: ${shipId}`)
        this.api.get(`/wows/encyclopedia/ships/?ship_id=${shipId}`)
          .then(response => {
            const ship = R.path(['data', 'data', shipId], response)
            if (ship) {
              this.shipdb.setName(shipId, ship.name)
              return resolve(ship.name)
            } else {
              return reject(Error('Ship not found.'))
            }
          })
          .catch(error => {
            console.log(error)
            return reject(error)
          })
      }
    })
  }

  getPlayerShip (shipId, accountId, matchGroup = 'pvp') {
    if (matchGroup === 'ranked') matchGroup = 'rank_solo'
    else if (matchGroup === 'cooperative') matchGroup = 'pve'
    return new Promise((resolve, reject) => {
      const params = {
        account_id: accountId,
        ship_id: shipId
      }
      if (matchGroup === 'rank_solo') params.extra = 'rank_solo'
      if (matchGroup === 'pve') params.extra = 'pve'
      this.api.get('/wows/ships/stats/', { params: params })
        .then(response => {
          const ship = R.path(['data', 'data', accountId], response)
          if (!ship) {
            return reject(Error('No ship data found.'))
          }

          const shipStats = ship[0]
          if (!R.prop(matchGroup, shipStats)) {
            return reject(Error('Player has no record in the selected matchGroup'))
          }

          const group = shipStats[matchGroup]
          const shipData = {
            battles: group.battles,
            victories: group.wins,
            winrate: group.battles ? (group.wins / group.battles * 100) : 0,
            survived: group.survived_battles,
            frags: group.frags,
            avgExp: group.battles ? (group.xp / group.battles) : 0,
            avgDmg: group.battles ? (group.damage_dealt / group.battles) : 0,
            kdRatio: (group.battles - group.survived_battles) ? (group.frags / (group.battles - group.survived_battles)) : 0,
            pr: 0
          }

          if (this.shipdb.has(shipId)) {
            // PR Calculation courtesy of http://wows-numbers.com/de/personal/rating
            const exp = this.shipdb.get(shipId)

            if (exp.average_damage_dealt && exp.win_rate && (exp.average_frags * shipData.battles)) {
              const rDmg = shipData.avgDmg / exp.average_damage_dealt
              const rWins = shipData.winrate / exp.win_rate
              const rFrags = shipData.frags / (exp.average_frags * shipData.battles)

              const nDmg = Math.max(0, (rDmg - 0.4) / (1 - 0.4))
              const nWins = Math.max(0, (rWins - 0.7) / (1 - 0.7))
              const nFrags = Math.max(0, (rFrags - 0.1) / (1 - 0.1))

              shipData.pr = (700 * nDmg + 300 * nFrags + 150 * nWins)
            } else {
              shipData.pr = 0
            }
          }
          return resolve(shipData)
        })
        .catch(error => {
          console.log(error)
          return reject(error)
        })
    })
  }
}
<commit_msg>Code cleanup<commit_after>import axios from 'axios'
import * as log from 'electron-log'
import R from 'ramda'

export class WowsApi {
  constructor (key, url, shipdb) {
    this.api = axios.create({
      baseURL: url,
      timeout: 20000,
      params: {
        application_id: key
      }
    })

    const responseHandler = (response) => {
      if (response.data.status !== 'ok' || R.path(['data', 'error'], response)) {
        return Promise.reject(R.path(['data', 'error'], response))
      } else {
        return response
      }
    }

    const errorHandler = (error) => {
      return Promise.reject(error)
    }

    this.api.interceptors.response.use(responseHandler, errorHandler)

    this.shipdb = shipdb
    this.key = key
    this.url = url
  }

  clearCache () {
    this.shipdb.clear()
  }

  findPlayer (playerName) {
    return new Promise((resolve, reject) => {
      // Find the player's account ID. We use Wargaming's
      // API to search by the player's name. The API returns a list of
      // players whose names match our search (i.e. searching for 'Alfred'
      // returns 'Alfred1', 'Alfred_accurate', 'AlfredAlfred' an so on), so
      // we have to search the response list ourselves for the right player
      this.api.get(`/wows/account/list/?search=${encodeURIComponent(playerName)}`)
        .then(response => {
          const playerRecord = R.find(R.propEq('nickname', playerName))(response.data.data)
          if (playerRecord) {
            return resolve({
              // Player found, so return his account ID
              accountId: playerRecord.account_id,
              name: playerRecord.nickname
            })
          } else {
            return reject(Error('Player not found'))
          }
        })
        .catch(error => {
          console.log(error)
          return Error('Player not found')
        })
    })
  }

  getPlayers (accountIds, matchGroup = 'pvp') {
    if (matchGroup === 'ranked') matchGroup = 'rank_solo'
    else if (matchGroup === 'cooperative') matchGroup = 'pve'
    return new Promise((resolve, reject) => {
      // Our second step is looking up the player's stats using their account IDs.
      const params = {
        account_id: R.map(R.prop('accountId'), accountIds).join(','),
        extra: ''
      }
      if (matchGroup === 'rank_solo') params.extra = 'statistics.rank_solo'
      if (matchGroup === 'pve') params.extra = 'statistics.pve'

      this.api.get('/wows/account/info/', { params: params })
        .then(response => {
          const players = R.mapObjIndexed((playerStats, accountId, obj) => {
            if (playerStats && (!playerStats.statistics || playerStats.hidden_profile || !R.path(['statistics', matchGroup], playerStats))) {
              return {
                name: playerStats.nickname,
                hidden: true
              }
            } else {
              const group = playerStats.statistics[matchGroup]
              return {
                battles: group.battles,
                winrate: (group.wins / group.battles * 100),
                avgExp: (group.xp / group.battles),
                avgDmg: (group.damage_dealt / group.battles),
                kdRatio: (group.frags / (group.battles - group.survived_battles)),
                name: playerStats.nickname
              }
            }
          })(response.data.data)
          return resolve(players)
        })
        .catch(error => {
          return reject(error)
        })
    })
  }

  getPlayerClan (accountId) {
    return new Promise((resolve, reject) => {
      this.api.get('/wows/clans/accountinfo/', { params: { account_id: accountId, extra: 'clan' } })
        .then(response => {
          const clan = R.path(['data', 'data', accountId, 'clan'], response)
          return clan
            ? resolve({
              id: clan.clan_id,
              createdAt: clan.created_at,
              membersCount: clan.members_count,
              name: clan.name,
              tag: clan.tag
            })
            : reject(Error('Player is not in a clan.'))
        })
        .catch(error => {
          console.log(error)
          return reject(error)
        })
    })
  }

  getShipName (shipId) {
    return new Promise((resolve, reject) => {
      if (this.shipdb.hasName(shipId)) {
        let shipName = this.shipdb.getName(shipId)
        log.info(`Cache hit: ${shipId} => ${shipName}`)
        return resolve(shipName)
      } else {
        log.info(`Cache miss: ${shipId}`)
        this.api.get(`/wows/encyclopedia/ships/?ship_id=${shipId}`)
          .then(response => {
            const ship = R.path(['data', 'data', shipId], response)
            if (ship) {
              this.shipdb.setName(shipId, ship.name)
              return resolve(ship.name)
            } else {
              return reject(Error('Ship not found.'))
            }
          })
          .catch(error => {
            console.log(error)
            return reject(error)
          })
      }
    })
  }

  getPlayerShip (shipId, accountId, matchGroup = 'pvp') {
    if (matchGroup === 'ranked') matchGroup = 'rank_solo'
    else if (matchGroup === 'cooperative') matchGroup = 'pve'
    return new Promise((resolve, reject) => {
      const params = {
        account_id: accountId,
        ship_id: shipId
      }
      if (matchGroup === 'rank_solo') params.extra = 'rank_solo'
      if (matchGroup === 'pve') params.extra = 'pve'
      this.api.get('/wows/ships/stats/', { params: params })
        .then(response => {
          const ship = R.path(['data', 'data', accountId], response)
          if (!ship) {
            return reject(Error('No ship data found.'))
          }

          const shipStats = ship[0]
          if (!R.prop(matchGroup, shipStats)) {
            return reject(Error('Player has no record in the selected matchGroup'))
          }

          const group = shipStats[matchGroup]
          const shipData = {
            battles: group.battles,
            victories: group.wins,
            winrate: group.battles ? (group.wins / group.battles * 100) : 0,
            survived: group.survived_battles,
            frags: group.frags,
            avgExp: group.battles ? (group.xp / group.battles) : 0,
            avgDmg: group.battles ? (group.damage_dealt / group.battles) : 0,
            kdRatio: (group.battles - group.survived_battles) ? (group.frags / (group.battles - group.survived_battles)) : 0,
            pr: 0
          }

          if (this.shipdb.has(shipId)) {
            // PR Calculation courtesy of http://wows-numbers.com/de/personal/rating
            const exp = this.shipdb.get(shipId)

            if (exp.average_damage_dealt && exp.win_rate && (exp.average_frags * shipData.battles)) {
              const rDmg = shipData.avgDmg / exp.average_damage_dealt
              const rWins = shipData.winrate / exp.win_rate
              const rFrags = shipData.frags / (exp.average_frags * shipData.battles)

              const nDmg = Math.max(0, (rDmg - 0.4) / (1 - 0.4))
              const nWins = Math.max(0, (rWins - 0.7) / (1 - 0.7))
              const nFrags = Math.max(0, (rFrags - 0.1) / (1 - 0.1))

              shipData.pr = (700 * nDmg + 300 * nFrags + 150 * nWins)
            } else {
              shipData.pr = 0
            }
          }
          return resolve(shipData)
        })
        .catch(error => {
          console.log(error)
          return reject(error)
        })
    })
  }
}
<|endoftext|>


<commit_before>import * as utils from 'base/utils';
import Hook from 'models/hook';

/*!
 * VIZABI Color Model (hook)
 */

var defaultPalettes = {
  "_continuous": {
    "_default": "#ffb600",
    "0": "#B4DE79",
    "50": "#E1CE00",
    "100": "#F77481"
  },
  "_discrete": {
    "_default": "#ffb600",
    "0": "#bcfa83",
    "1": "#4cd843",
    "2": "#ff8684",
    "3": "#e83739",
    "4": "#ffb04b",
    "5": "#ff7f00",
    "6": "#f599f5",
    "7": "#c027d4",
    "8": "#f4f459",
    "9": "#d66425",
    "10": "#7fb5ed",
    "11": "#0ab8d8"
  },
  "_default": {
    "_default": "#93daec"
  }
};

var ColorModel = Hook.extend({

  /**
   * Default values for this model
   */
  getClassDefaults: function() {
    var defaults = {
      use: "constant",
      which: "_default",
      scaleType: "ordinal",
      syncModels: [],
      palette: {},
      paletteLabels: null,
      allow: {
        //this is almost everything, but not "nominal", so no random strings like "name"
        scales: ["linear", "log", "genericLog", "time", "pow", "ordinal"]
      }
    };
    return utils.deepExtend(this._super(), defaults)
  },

  /**
   * Initializes the color hook
   * @param {Object} values The initial values of this model
   * @param parent A reference to the parent model
   * @param {Object} bind Initial events to bind
   */
  init: function(name, values, parent, bind) {
    var _this = this;
    this._type = "color";

    this._super(name, values, parent, bind);

    this._syncModelReferences = {};
    this._hasDefaultColor = false;

    this.on('hook_change', function() {
      if(_this._readyOnce) return;

      if(_this.palette && Object.keys(_this.palette._data).length!==0) {
        var defaultPalette = _this.getDefaultPalette();
        var currentPalette = _this.getPalette(true);
        var palette = {};
        //extend partial current palette with default palette and
        //switch current palette elements which equals
        //default palette elments to nonpersistent state
        Object.keys(defaultPalette).map(function(key) {
          if(!currentPalette[key]||defaultPalette[key]==currentPalette[key]) palette[key] = defaultPalette[key];
        });
        _this.set("palette", palette, false, false);
      }
    });
  },

  setInterModelListeners: function () {
    this._super();
    this._setSyncModels();
  },

  // args: {colorID, shadeID}
  getColorShade: function(args){
    var palette = this.getPalette();

    if(!args) return utils.warn("getColorShade() is missing arguments");

    // if colorID is not given or not found in the palette, replace it with default color
    if(!args.colorID || !palette[args.colorID]) args.colorID = "_default";

    // if the resolved colr value is not an array (has only one shade) -- return it
    if( !utils.isArray(palette[args.colorID]) ) return palette[args.colorID];

    var conceptpropsColor = this.getConceptprops().color;
    var shade = args.shadeID && conceptpropsColor && conceptpropsColor.shades && conceptpropsColor.shades[args.shadeID] ? conceptpropsColor.shades[args.shadeID] : 0;

    return palette[args.colorID][shade];

  },

  /**
   * Get the above constants
   */
  isUserSelectable: function() {
    var conceptpropsColor = this.getConceptprops().color;
    return conceptpropsColor == null || conceptpropsColor.selectable == null || conceptpropsColor.selectable;
  },

  setWhich: function(newValue) {
    this._super(newValue);
    if(this.palette) this.palette._data = {};
    this._setSyncModels();
  },

  _setSyncModels: function() {
    var _this = this;
    this.syncModels.forEach(function(modelName){
      //fetch the model to sync, it's marker and entities
      var model = _this.getClosestModel(modelName);
      var marker = model.isHook()? model._parent : model;
      var entities = marker.getClosestModel(marker.space[0]);

      //save the references here locally
      _this._syncModelReferences[modelName] = {model: model, marker: marker, entities: entities};

      if(_this.isDiscrete()) _this._setSyncModel(model, marker, entities);
    });
  },

  _setSyncModel: function(model, marker, entities) {
    if(model == marker){
      var newFilter = {
        dim: this.which,
        show: {}
      };
      marker.setDataSourceForAllSubhooks(this.data);
      entities.set(newFilter, false, false);
    }else{
      if(model.isDiscrete() && model.use !== "constant") model.set({which: this.which, data: this.data}, false, false);
    }
  },

  getColorlegendMarker: function() {
    return (this._syncModelReferences["marker_colorlegend"]||{})["marker"];
  },

  getColorlegendEntities: function() {
    return (this._syncModelReferences["marker_colorlegend"]||{})["entities"];
  },

  /**
   * set color
   */
  setColor: function(value, pointer) {
    var temp = this.getPalette();
    temp[pointer] = value;
    this.scale.range(utils.values(temp));
    this.palette[pointer] = value;
  },

  /**
   * maps the value to this hook's specifications
   * @param value Original value
   * @returns hooked value
   */
  mapValue: function(value) {
    //if the property value does not exist, supply the _default
    // otherwise the missing value would be added to the domain
    if(this.scale != null && this.isDiscrete() && this._hasDefaultColor && this.scale.domain().indexOf(value) == -1) value = "_default";
    return this._super(value);
  },

  getDefaultPalette: function() {
      var conceptpropsColor = this.getConceptprops().color;
      var palette;

      this.discreteDefaultPalette = false;

      if(conceptpropsColor && conceptpropsColor.palette) {
        //specific color palette from hook concept properties
        palette = utils.clone(conceptpropsColor.palette);
      } else if(defaultPalettes[this.which]) {
        //color palette for this.which exists in palette defaults
        palette = utils.clone(defaultPalettes[this.which]);
      } else if(this.use === "constant") {
        //an explicit hex color constant #abc or #adcdef is provided
        if(/^#([0-9a-f]{3}|[0-9a-f]{6})$/.test(this.which)){
          palette = {"_default": this.which};
        }else{
          palette = utils.clone(defaultPalettes["_default"]);
        }
      } else {
        palette = utils.clone(defaultPalettes[this.isDiscrete()? "_discrete" : "_continuous"]);
        this.discreteDefaultPalette = true;
     }

      return palette;
  },

  _getPaletteLabels: function() {
      var conceptpropsColor = this.getConceptprops().color;
      var paletteLabels = null;

      if(conceptpropsColor && conceptpropsColor.paletteLabels) {
        //specific color palette from hook concept properties
        paletteLabels = utils.clone(conceptpropsColor.paletteLabels);
      }
      return paletteLabels;
  },

  getPaletteLabels: function() {
    return this.paletteLabels.getPlainObject();
  },

  getPalette: function(includeDefault){
    //rebuild palette if it's empty
    if (!this.palette || Object.keys(this.palette._data).length===0){
      var palette = this.getDefaultPalette();
      this.set("palette", palette, false, false);
      var paletteLabels = this._getPaletteLabels();
      this.set("paletteLabels", paletteLabels, false, false);
    }
    var palette = this.palette.getPlainObject();
    if(this.use === "indicator" && !includeDefault) {
      delete palette["_default"];
    }
    return palette;
  },

  /**
   * Gets the domain for this hook
   * @returns {Array} domain
   */
  buildScale: function(scaleType = this.scaleType) {
    var _this = this;

    var paletteObject = _this.getPalette();
    var domain = Object.keys(paletteObject);
    var range = utils.values(paletteObject);

    this._hasDefaultColor = domain.indexOf("_default") > -1;

    if(scaleType == "time") {

      var timeMdl = this._space.time;
      var limits = timeMdl.splash ?
          {min: timeMdl.parse(timeMdl.startOrigin), max: timeMdl.parse(timeMdl.endOrigin)}
          :
          {min: timeMdl.start, max: timeMdl.end};

      var singlePoint = (limits.max - limits.min == 0);

      domain = domain.sort((a,b) => a-b);
      range = domain.map((m) => singlePoint? paletteObject[domain[0]] : paletteObject[m]);
      domain = domain.map((m) => limits.min.valueOf() + m/100 * (limits.max.valueOf() - limits.min.valueOf()));

      this.scale = d3.time.scale.utc()
        .domain(domain)
        .range(range)
        .interpolate(d3.interpolateRgb);

    }else if(!this.isDiscrete()){

      var limits = this.getLimits(this.which);
      //default domain is based on limits
      limits = [limits.min, limits.max];
      //domain from concept properties can override it if defined
      limits = this.getConceptprops().domain ? this.getConceptprops().domain : limits;

      var singlePoint = (limits[1] - limits[0] == 0);

      domain = domain.sort((a,b) => a-b);
      range = domain.map((m) => singlePoint? paletteObject[domain[0]] : paletteObject[m]);
      domain = domain.map((m) => limits[0] + m/100 * (limits[1] - limits[0]));

      if(d3.min(domain)<=0 && d3.max(domain)>=0 && scaleType === "log") scaleType = "genericLog";

      if(scaleType == "log" || scaleType == "genericLog") {
        var s = d3.scale.genericLog()
          .domain(limits)
          .range(limits);
        domain = domain.map((d) => s.invert(d));
      }
      this.scale = d3.scale[scaleType]()
        .domain(domain)
        .range(range)
        .interpolate(d3.interpolateRgb);

    }else{
      range = range.map((m) => utils.isArray(m)? m[0] : m);

      scaleType = "ordinal";

      if(this.discreteDefaultPalette) domain = domain.concat(this.getUnique(this.which));
      this.scale = d3.scale[scaleType]()
        .domain(domain)
        .range(range);
    }

    this.scaleType = scaleType;
  }

});

export default ColorModel;
<commit_msg>Change the order of the default colors so that different ones come first<commit_after>import * as utils from 'base/utils';
import Hook from 'models/hook';

/*!
 * VIZABI Color Model (hook)
 */

var defaultPalettes = {
  "_continuous": {
    "_default": "#ffb600",
    "0": "#B4DE79",
    "50": "#E1CE00",
    "100": "#F77481"
  },
  "_discrete": {
    "_default": "#ffb600",
    "0": "#4cd843",
    "1": "#e83739",
    "2": "#ff7f00",
    "3": "#c027d4",
    "4": "#d66425",
    "5": "#0ab8d8",
    "6": "#bcfa83",
    "7": "#ff8684",
    "8": "#ffb04b",
    "9": "#f599f5",
    "10": "#f4f459",
    "11": "#7fb5ed"
  },
  "_default": {
    "_default": "#93daec"
  }
};

var ColorModel = Hook.extend({

  /**
   * Default values for this model
   */
  getClassDefaults: function() {
    var defaults = {
      use: "constant",
      which: "_default",
      scaleType: "ordinal",
      syncModels: [],
      palette: {},
      paletteLabels: null,
      allow: {
        //this is almost everything, but not "nominal", so no random strings like "name"
        scales: ["linear", "log", "genericLog", "time", "pow", "ordinal"]
      }
    };
    return utils.deepExtend(this._super(), defaults)
  },

  /**
   * Initializes the color hook
   * @param {Object} values The initial values of this model
   * @param parent A reference to the parent model
   * @param {Object} bind Initial events to bind
   */
  init: function(name, values, parent, bind) {
    var _this = this;
    this._type = "color";

    this._super(name, values, parent, bind);

    this._syncModelReferences = {};
    this._hasDefaultColor = false;

    this.on('hook_change', function() {
      if(_this._readyOnce) return;

      if(_this.palette && Object.keys(_this.palette._data).length!==0) {
        var defaultPalette = _this.getDefaultPalette();
        var currentPalette = _this.getPalette(true);
        var palette = {};
        //extend partial current palette with default palette and
        //switch current palette elements which equals
        //default palette elments to nonpersistent state
        Object.keys(defaultPalette).map(function(key) {
          if(!currentPalette[key]||defaultPalette[key]==currentPalette[key]) palette[key] = defaultPalette[key];
        });
        _this.set("palette", palette, false, false);
      }
    });
  },

  setInterModelListeners: function () {
    this._super();
    this._setSyncModels();
  },

  // args: {colorID, shadeID}
  getColorShade: function(args){
    var palette = this.getPalette();

    if(!args) return utils.warn("getColorShade() is missing arguments");

    // if colorID is not given or not found in the palette, replace it with default color
    if(!args.colorID || !palette[args.colorID]) args.colorID = "_default";

    // if the resolved colr value is not an array (has only one shade) -- return it
    if( !utils.isArray(palette[args.colorID]) ) return palette[args.colorID];

    var conceptpropsColor = this.getConceptprops().color;
    var shade = args.shadeID && conceptpropsColor && conceptpropsColor.shades && conceptpropsColor.shades[args.shadeID] ? conceptpropsColor.shades[args.shadeID] : 0;

    return palette[args.colorID][shade];

  },

  /**
   * Get the above constants
   */
  isUserSelectable: function() {
    var conceptpropsColor = this.getConceptprops().color;
    return conceptpropsColor == null || conceptpropsColor.selectable == null || conceptpropsColor.selectable;
  },

  setWhich: function(newValue) {
    this._super(newValue);
    if(this.palette) this.palette._data = {};
    this._setSyncModels();
  },

  _setSyncModels: function() {
    var _this = this;
    this.syncModels.forEach(function(modelName){
      //fetch the model to sync, it's marker and entities
      var model = _this.getClosestModel(modelName);
      var marker = model.isHook()? model._parent : model;
      var entities = marker.getClosestModel(marker.space[0]);

      //save the references here locally
      _this._syncModelReferences[modelName] = {model: model, marker: marker, entities: entities};

      if(_this.isDiscrete()) _this._setSyncModel(model, marker, entities);
    });
  },

  _setSyncModel: function(model, marker, entities) {
    if(model == marker){
      var newFilter = {
        dim: this.which,
        show: {}
      };
      marker.setDataSourceForAllSubhooks(this.data);
      entities.set(newFilter, false, false);
    }else{
      if(model.isDiscrete() && model.use !== "constant") model.set({which: this.which, data: this.data}, false, false);
    }
  },

  getColorlegendMarker: function() {
    return (this._syncModelReferences["marker_colorlegend"]||{})["marker"];
  },

  getColorlegendEntities: function() {
    return (this._syncModelReferences["marker_colorlegend"]||{})["entities"];
  },

  /**
   * set color
   */
  setColor: function(value, pointer) {
    var temp = this.getPalette();
    temp[pointer] = value;
    this.scale.range(utils.values(temp));
    this.palette[pointer] = value;
  },

  /**
   * maps the value to this hook's specifications
   * @param value Original value
   * @returns hooked value
   */
  mapValue: function(value) {
    //if the property value does not exist, supply the _default
    // otherwise the missing value would be added to the domain
    if(this.scale != null && this.isDiscrete() && this._hasDefaultColor && this.scale.domain().indexOf(value) == -1) value = "_default";
    return this._super(value);
  },

  getDefaultPalette: function() {
      var conceptpropsColor = this.getConceptprops().color;
      var palette;

      this.discreteDefaultPalette = false;

      if(conceptpropsColor && conceptpropsColor.palette) {
        //specific color palette from hook concept properties
        palette = utils.clone(conceptpropsColor.palette);
      } else if(defaultPalettes[this.which]) {
        //color palette for this.which exists in palette defaults
        palette = utils.clone(defaultPalettes[this.which]);
      } else if(this.use === "constant") {
        //an explicit hex color constant #abc or #adcdef is provided
        if(/^#([0-9a-f]{3}|[0-9a-f]{6})$/.test(this.which)){
          palette = {"_default": this.which};
        }else{
          palette = utils.clone(defaultPalettes["_default"]);
        }
      } else {
        palette = utils.clone(defaultPalettes[this.isDiscrete()? "_discrete" : "_continuous"]);
        this.discreteDefaultPalette = true;
     }

      return palette;
  },

  _getPaletteLabels: function() {
      var conceptpropsColor = this.getConceptprops().color;
      var paletteLabels = null;

      if(conceptpropsColor && conceptpropsColor.paletteLabels) {
        //specific color palette from hook concept properties
        paletteLabels = utils.clone(conceptpropsColor.paletteLabels);
      }
      return paletteLabels;
  },

  getPaletteLabels: function() {
    return this.paletteLabels.getPlainObject();
  },

  getPalette: function(includeDefault){
    //rebuild palette if it's empty
    if (!this.palette || Object.keys(this.palette._data).length===0){
      var palette = this.getDefaultPalette();
      this.set("palette", palette, false, false);
      var paletteLabels = this._getPaletteLabels();
      this.set("paletteLabels", paletteLabels, false, false);
    }
    var palette = this.palette.getPlainObject();
    if(this.use === "indicator" && !includeDefault) {
      delete palette["_default"];
    }
    return palette;
  },

  /**
   * Gets the domain for this hook
   * @returns {Array} domain
   */
  buildScale: function(scaleType = this.scaleType) {
    var _this = this;

    var paletteObject = _this.getPalette();
    var domain = Object.keys(paletteObject);
    var range = utils.values(paletteObject);

    this._hasDefaultColor = domain.indexOf("_default") > -1;

    if(scaleType == "time") {

      var timeMdl = this._space.time;
      var limits = timeMdl.splash ?
          {min: timeMdl.parse(timeMdl.startOrigin), max: timeMdl.parse(timeMdl.endOrigin)}
          :
          {min: timeMdl.start, max: timeMdl.end};

      var singlePoint = (limits.max - limits.min == 0);

      domain = domain.sort((a,b) => a-b);
      range = domain.map((m) => singlePoint? paletteObject[domain[0]] : paletteObject[m]);
      domain = domain.map((m) => limits.min.valueOf() + m/100 * (limits.max.valueOf() - limits.min.valueOf()));

      this.scale = d3.time.scale.utc()
        .domain(domain)
        .range(range)
        .interpolate(d3.interpolateRgb);

    }else if(!this.isDiscrete()){

      var limits = this.getLimits(this.which);
      //default domain is based on limits
      limits = [limits.min, limits.max];
      //domain from concept properties can override it if defined
      limits = this.getConceptprops().domain ? this.getConceptprops().domain : limits;

      var singlePoint = (limits[1] - limits[0] == 0);

      domain = domain.sort((a,b) => a-b);
      range = domain.map((m) => singlePoint? paletteObject[domain[0]] : paletteObject[m]);
      domain = domain.map((m) => limits[0] + m/100 * (limits[1] - limits[0]));

      if(d3.min(domain)<=0 && d3.max(domain)>=0 && scaleType === "log") scaleType = "genericLog";

      if(scaleType == "log" || scaleType == "genericLog") {
        var s = d3.scale.genericLog()
          .domain(limits)
          .range(limits);
        domain = domain.map((d) => s.invert(d));
      }
      this.scale = d3.scale[scaleType]()
        .domain(domain)
        .range(range)
        .interpolate(d3.interpolateRgb);

    }else{
      range = range.map((m) => utils.isArray(m)? m[0] : m);

      scaleType = "ordinal";

      if(this.discreteDefaultPalette) domain = domain.concat(this.getUnique(this.which));
      this.scale = d3.scale[scaleType]()
        .domain(domain)
        .range(range);
    }

    this.scaleType = scaleType;
  }

});

export default ColorModel;
<|endoftext|>


<commit_before>/**
 * CTA component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import classnames from 'classnames';

/**
 * Internal dependencies
 */
import Link from '../Link';

const CTA = ( {
	title,
	description,
	ctaLink,
	ctaLabel,
	ctaLinkExternal,
	error,
	onClick,
	'aria-label': ariaLabel,
} ) => (

		{ title &&
{ title }
 }
		{ description && typeof description === 'string' && (

{ description }

		) }
		{ description && typeof description !== 'string' && (

				{ description }

		) }
		{ ctaLabel && (
			<Link
				href={ ctaLink }
				onClick={ onClick }
				inverse={ ! error }
				caps
				arrow
				aria-label={ ariaLabel }
				external={ ctaLinkExternal }
				hideExternalIndicator={ ctaLinkExternal }
			>
				{ ctaLabel }
			</Link>
		) }

);

CTA.propTypes = {
	title: PropTypes.string.isRequired,
	description: PropTypes.oneOfType( [ PropTypes.string, PropTypes.node ] ),
	ctaLink: PropTypes.string,
	ctaLinkExternal: PropTypes.bool,
	ctaLabel: PropTypes.string,
	'aria-label': PropTypes.string,
	error: PropTypes.bool,
	onClick: PropTypes.func,
};

CTA.defaultProps = {
	title: '',
	description: '',
	ctaLink: '',
	ctaLabel: '',
	error: false,
	onClick: () => {},
};

export default CTA;
<commit_msg>Add retry button in the CTA component.<commit_after>/**
 * CTA component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import classnames from 'classnames';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Link from '../Link';
import Button from '../Button';

const CTA = ( {
	title,
	description,
	ctaLink,
	ctaLabel,
	ctaLinkExternal,
	error,
	onClick,
	retry,
	onRetry,
	'aria-label': ariaLabel,
} ) => (

		{ title &&
{ title }
 }
		{ description && typeof description === 'string' && (

{ description }

		) }
		{ description && typeof description !== 'string' && (

				{ description }

		) }
		{ retry && (
			<Button onClick={ onRetry }>
				{ __( 'Retry', 'google-site-kit' ) }
			</Button>
		) }
		{ ctaLabel && (
			<Link
				href={ ctaLink }
				onClick={ onClick }
				inverse={ ! error }
				caps
				arrow
				aria-label={ ariaLabel }
				external={ ctaLinkExternal }
				hideExternalIndicator={ ctaLinkExternal }
			>
				{ ctaLabel }
			</Link>
		) }

);

CTA.propTypes = {
	title: PropTypes.string.isRequired,
	description: PropTypes.oneOfType( [ PropTypes.string, PropTypes.node ] ),
	ctaLink: PropTypes.string,
	ctaLinkExternal: PropTypes.bool,
	ctaLabel: PropTypes.string,
	'aria-label': PropTypes.string,
	error: PropTypes.bool,
	onClick: PropTypes.func,
	retry: PropTypes.bool,
	onRetry: PropTypes.func,
};

CTA.defaultProps = {
	title: '',
	description: '',
	ctaLink: '',
	ctaLabel: '',
	error: false,
	onClick: () => {},
	retry: false,
	onRetry: () => {},
};

export default CTA;
<|endoftext|>


<commit_before>// @flow

import chalk from 'chalk';
import fse from 'fs-extra';
import path from 'path';
import pathExists from 'path-exists';
import spawn from 'cross-spawn';
import log from '../util/log';

// UPDATE DEPENDENCY VERSIONS HERE
const DEFAULT_DEPENDENCIES = {
  expo: '^19.0.0',
  react: '16.0.0-alpha.12',
  'react-native': '^0.46.1',
};

// TODO figure out how this interacts with ejection
const DEFAULT_DEV_DEPENDENCIES = {
  'jest-expo': '~19.0.0',
  'react-test-renderer': '16.0.0-alpha.12',
};

module.exports = async (appPath: string, appName: string, verbose: boolean, cwd: string = '') => {
  const ownPackageName: string = require('../../package.json').name;
  const ownPath: string = path.join(appPath, 'node_modules', ownPackageName);
  const useYarn: boolean = await pathExists(path.join(appPath, 'yarn.lock'));

  // FIXME(perry) remove when npm 5 is supported
  if (!useYarn) {
    let npmVersion = spawn.sync('npm', ['--version']).stdout.toString().trim();

    if (npmVersion.match(/\d+/)[0] === '5') {
      console.log(
        chalk.yellow(
          `
*******************************************************************************
ERROR: npm 5 is not supported yet
*******************************************************************************

It looks like you're using npm 5 which was recently released.

Create React Native App doesn't work with npm 5 yet, unfortunately. We
recommend using npm 4 or yarn until some bugs are resolved.

You can follow the known issues with npm 5 at:
https://github.com/npm/npm/issues/16991

*******************************************************************************
 `
        )
      );
      process.exit(1);
    }
  }

  const readmeExists: boolean = await pathExists(path.join(appPath, 'README.md'));
  if (readmeExists) {
    await fse.rename(path.join(appPath, 'README.md'), path.join(appPath, 'README.old.md'));
  }

  const appPackagePath: string = path.join(appPath, 'package.json');
  const appPackage = JSON.parse(await fse.readFile(appPackagePath));

  // mutate the default package.json in any ways we need to
  appPackage.main = './node_modules/react-native-scripts/build/bin/crna-entry.js';
  appPackage.scripts = {
    start: 'react-native-scripts start',
    eject: 'react-native-scripts eject',
    android: 'react-native-scripts android',
    ios: 'react-native-scripts ios',
    test: 'node node_modules/jest/bin/jest.js --watch',
  };

  appPackage.jest = {
    preset: 'jest-expo',
  };

  if (!appPackage.dependencies) {
    appPackage.dependencies = {};
  }

  if (!appPackage.devDependencies) {
    appPackage.devDependencies = {};
  }

  // react-native-scripts is already in the package.json devDependencies
  // so we need to merge instead of assigning
  Object.assign(appPackage.dependencies, DEFAULT_DEPENDENCIES);
  Object.assign(appPackage.devDependencies, DEFAULT_DEV_DEPENDENCIES);

  // Write the new appPackage after copying so that we can include any existing
  await fse.writeFile(appPackagePath, JSON.stringify(appPackage, null, 2));

  // Copy the files for the user
  await fse.copy(path.join(ownPath, 'template'), appPath);

  // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
  try {
    await fse.rename(path.join(appPath, 'gitignore'), path.join(appPath, '.gitignore'));
  } catch (err) {
    // Append if there's already a `.gitignore` file there
    if (err.code === 'EEXIST') {
      const data = await fse.readFile(path.join(appPath, 'gitignore'));
      await fse.appendFile(path.join(appPath, '.gitignore'), data);
      await fse.unlink(path.join(appPath, 'gitignore'));
    } else {
      throw err;
    }
  }

  // Run yarn or npm
  let command = '';
  let args = [];

  if (useYarn) {
    command = 'yarnpkg';
  } else {
    command = 'npm';
    args = ['install', '--save'];

    if (verbose) {
      args.push('--verbose');
    }
  }

  log(`Installing dependencies using ${command}...`);
  log(); // why is this here

  if (command === 'yarnpkg') {
    // it's weird to print a yarn alias that no one uses
    command = 'yarn';
  }

  const proc = spawn(command, args, { stdio: 'inherit' });
  proc.on('close', code => {
    if (code !== 0) {
      console.error(`\`${command} ${args.join(' ')}\` failed`);
      return;
    }

    // display the cleanest way to get to the app dir
    // if the cwd + appName is equal to the full path, then just cd into appName
    let cdpath;
    if (path.resolve(cwd, appName) === appPath) {
      cdpath = appName;
    } else {
      cdpath = appPath;
    }

    log(
      `

Success! Created ${appName} at ${appPath}
Inside that directory, you can run several commands:

  ${chalk.cyan(command + ' start')}
    Starts the development server so you can open your app in the Expo
    app on your phone.

  ${chalk.cyan(command + ' run ios')}
    (Mac only, requires Xcode)
    Starts the development server and loads your app in an iOS simulator.

  ${chalk.cyan(command + ' run android')}
    (Requires Android build tools)
    Starts the development server and loads your app on a connected Android
    device or emulator.

  ${chalk.cyan(command + ' test')}
    Starts the test runner.

  ${chalk.cyan(command + ' run eject')}
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  ${chalk.cyan('cd ' + cdpath)}
  ${chalk.cyan(command + ' start')}`
    );

    if (readmeExists) {
      log(
        `
${chalk.yellow('You had a `README.md` file, we renamed it to `README.old.md`')}`
      );
    }

    log();
    log('Happy hacking!');
  });
};
<commit_msg>Fix yarnpkg alias usage in the init script (#329)<commit_after>// @flow

import chalk from 'chalk';
import fse from 'fs-extra';
import path from 'path';
import pathExists from 'path-exists';
import spawn from 'cross-spawn';
import log from '../util/log';

// UPDATE DEPENDENCY VERSIONS HERE
const DEFAULT_DEPENDENCIES = {
  expo: '^19.0.0',
  react: '16.0.0-alpha.12',
  'react-native': '^0.46.1',
};

// TODO figure out how this interacts with ejection
const DEFAULT_DEV_DEPENDENCIES = {
  'jest-expo': '~19.0.0',
  'react-test-renderer': '16.0.0-alpha.12',
};

module.exports = async (appPath: string, appName: string, verbose: boolean, cwd: string = '') => {
  const ownPackageName: string = require('../../package.json').name;
  const ownPath: string = path.join(appPath, 'node_modules', ownPackageName);
  const useYarn: boolean = await pathExists(path.join(appPath, 'yarn.lock'));

  // FIXME(perry) remove when npm 5 is supported
  if (!useYarn) {
    let npmVersion = spawn.sync('npm', ['--version']).stdout.toString().trim();

    if (npmVersion.match(/\d+/)[0] === '5') {
      console.log(
        chalk.yellow(
          `
*******************************************************************************
ERROR: npm 5 is not supported yet
*******************************************************************************

It looks like you're using npm 5 which was recently released.

Create React Native App doesn't work with npm 5 yet, unfortunately. We
recommend using npm 4 or yarn until some bugs are resolved.

You can follow the known issues with npm 5 at:
https://github.com/npm/npm/issues/16991

*******************************************************************************
 `
        )
      );
      process.exit(1);
    }
  }

  const readmeExists: boolean = await pathExists(path.join(appPath, 'README.md'));
  if (readmeExists) {
    await fse.rename(path.join(appPath, 'README.md'), path.join(appPath, 'README.old.md'));
  }

  const appPackagePath: string = path.join(appPath, 'package.json');
  const appPackage = JSON.parse(await fse.readFile(appPackagePath));

  // mutate the default package.json in any ways we need to
  appPackage.main = './node_modules/react-native-scripts/build/bin/crna-entry.js';
  appPackage.scripts = {
    start: 'react-native-scripts start',
    eject: 'react-native-scripts eject',
    android: 'react-native-scripts android',
    ios: 'react-native-scripts ios',
    test: 'node node_modules/jest/bin/jest.js --watch',
  };

  appPackage.jest = {
    preset: 'jest-expo',
  };

  if (!appPackage.dependencies) {
    appPackage.dependencies = {};
  }

  if (!appPackage.devDependencies) {
    appPackage.devDependencies = {};
  }

  // react-native-scripts is already in the package.json devDependencies
  // so we need to merge instead of assigning
  Object.assign(appPackage.dependencies, DEFAULT_DEPENDENCIES);
  Object.assign(appPackage.devDependencies, DEFAULT_DEV_DEPENDENCIES);

  // Write the new appPackage after copying so that we can include any existing
  await fse.writeFile(appPackagePath, JSON.stringify(appPackage, null, 2));

  // Copy the files for the user
  await fse.copy(path.join(ownPath, 'template'), appPath);

  // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
  try {
    await fse.rename(path.join(appPath, 'gitignore'), path.join(appPath, '.gitignore'));
  } catch (err) {
    // Append if there's already a `.gitignore` file there
    if (err.code === 'EEXIST') {
      const data = await fse.readFile(path.join(appPath, 'gitignore'));
      await fse.appendFile(path.join(appPath, '.gitignore'), data);
      await fse.unlink(path.join(appPath, 'gitignore'));
    } else {
      throw err;
    }
  }

  // Run yarn or npm
  let command = '';
  let args = [];

  if (useYarn) {
    command = 'yarn';
  } else {
    command = 'npm';
    args = ['install', '--save'];

    if (verbose) {
      args.push('--verbose');
    }
  }

  log(`Installing dependencies using ${command}...`);
  log(); // why is this here

  if (command === 'yarn') {
    // Use the more unique `yarnpkg` alias to avoid naming conflicts with other tools.
    command = 'yarnpkg';
  }

  const proc = spawn(command, args, { stdio: 'inherit' });
  proc.on('close', code => {
    if (code !== 0) {
      console.error(`\`${command} ${args.join(' ')}\` failed`);
      return;
    }

    // display the cleanest way to get to the app dir
    // if the cwd + appName is equal to the full path, then just cd into appName
    let cdpath;
    if (path.resolve(cwd, appName) === appPath) {
      cdpath = appName;
    } else {
      cdpath = appPath;
    }

    log(
      `

Success! Created ${appName} at ${appPath}
Inside that directory, you can run several commands:

  ${chalk.cyan(command + ' start')}
    Starts the development server so you can open your app in the Expo
    app on your phone.

  ${chalk.cyan(command + ' run ios')}
    (Mac only, requires Xcode)
    Starts the development server and loads your app in an iOS simulator.

  ${chalk.cyan(command + ' run android')}
    (Requires Android build tools)
    Starts the development server and loads your app on a connected Android
    device or emulator.

  ${chalk.cyan(command + ' test')}
    Starts the test runner.

  ${chalk.cyan(command + ' run eject')}
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  ${chalk.cyan('cd ' + cdpath)}
  ${chalk.cyan(command + ' start')}`
    );

    if (readmeExists) {
      log(
        `
${chalk.yellow('You had a `README.md` file, we renamed it to `README.old.md`')}`
      );
    }

    log();
    log('Happy hacking!');
  });
};
<|endoftext|>