<commit_before>/**
 * @file ListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import Checkbox from '../Checkbox';
import Radio from '../Radio';
import CircularLoading from '../CircularLoading';
import TipProvider from '../TipProvider';
import TouchRipple from '../TouchRipple';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Position from '../_statics/Position';
import SelectMode from '../_statics/SelectMode';

class ListItem extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    multiSelectChangeHandler = checked => {

        const {onSelect, onDeselect} = this.props;

        if (checked) {
            onSelect && onSelect();
        } else {
            onDeselect && onDeselect();
        }

    };

    singleSelectChangeHandler = () => {
        if (!this.props.checked) {
            const {onSelect} = this.props;
            onSelect && onSelect();
        }
    };

    clickHandler = e => {

        const {disabled, isLoading, readOnly, autoSelect} = this.props;

        if (disabled || isLoading || readOnly) {
            return;
        }

        const {onClick} = this.props;
        onClick && onClick(e);

        if (!autoSelect) {
            return;
        }

        switch (this.props.selectMode) {
            case SelectMode.MULTI_SELECT:
                this.multiSelectChangeHandler(!this.props.checked);
                return;
            case SelectMode.SINGLE_SELECT:
                this.singleSelectChangeHandler();
                return;
        }

    };

    radioCheckHandler = () => {
        this.singleSelectChangeHandler();
    };

    checkboxCheckHandler = () => {
        this.multiSelectChangeHandler(true);
    };

    checkboxUncheckHandler = () => {
        this.multiSelectChangeHandler(false);
    };

    render() {

        const {

                index, className, style, theme, activatedTheme, data, text, desc, title, iconCls, rightIconCls,
                tip, tipPosition, disabled, isLoading, disableTouchRipple, rippleDisplayCenter, readOnly,
                renderer, itemRenderer,

                checked, selectTheme, selectMode, indeterminateCallback, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                onMouseEnter, onMouseLeave,
                parentEl

            } = this.props,

            listItemClassName = classNames('list-item', {
                [`theme-${theme}`]: !checked && theme,
                [`theme-${activatedTheme}`]: checked && activatedTheme,
                activated: checked,
                [className]: className
            }),

            loadingIconPosition = (rightIconCls && !iconCls) ? 'right' : 'left';

        return (
            <TipProvider tipContent={tip}
                         parentEl={parentEl}
                         position={tipPosition}>

                    {
                        selectMode === SelectMode.SINGLE_SELECT && (radioUncheckedIconCls || radioCheckedIconCls) ?
                            <Radio className="list-item-select"
                                   theme={selectTheme}
                                   checked={checked}
                                   disabled={disabled || isLoading}
                                   uncheckedIconCls={radioUncheckedIconCls}
                                   checkedIconCls={radioCheckedIconCls}
                                   disableTouchRipple={true}
                                   onCheck={this.radioCheckHandler}/>
                            :
                            null
                    }

                    {
                        selectMode === SelectMode.MULTI_SELECT ?
                            <Checkbox className="list-item-select"
                                      theme={selectTheme}
                                      checked={checked}
                                      indeterminate={indeterminateCallback && indeterminateCallback(data)}
                                      disabled={disabled || isLoading}
                                      uncheckedIconCls={checkboxUncheckedIconCls}
                                      checkedIconCls={checkboxCheckedIconCls}
                                      indeterminateIconCls={checkboxIndeterminateIconCls}
                                      disableTouchRipple={true}
                                      onCheck={this.checkboxCheckHandler}
                                      onUncheck={this.checkboxUncheckHandler}/>
                            :
                            null
                    }

                    {
                        isLoading && loadingIconPosition === 'left' ?

                                <CircularLoading className="button-loading-icon"
                                                 size="small"/>

                            :
                            (
                                iconCls ?
                                    <i className={`button-icon button-icon-left ${iconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        itemRenderer && typeof itemRenderer === 'function' ?
                            itemRenderer(data, index)
                            :
                            (
                                renderer && typeof renderer === 'function' ?
                                    renderer(data, index)
                                    :
                                    (
                                        desc ?

                                                    {text}

                                                    {desc}

                                            :
                                            text
                                    )
                            )
                    }

                    {
                        isLoading && loadingIconPosition === 'right' ?
                            <CircularLoading className="button-icon button-icon-right button-loading-icon"
                                             size="small"/>
                            :
                            (
                                rightIconCls ?
                                    <i className={`button-icon button-icon-right ${rightIconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        disableTouchRipple || readOnly ?
                            null
                            :
                            <TouchRipple ref="touchRipple"
                                         className={disabled || isLoading ? 'hidden' : ''}
                                         displayCenter={rippleDisplayCenter}/>
                    }

            </TipProvider>
        );

    }
}

ListItem.propTypes = {

    index: PropTypes.number,

    className: PropTypes.string,
    style: PropTypes.object,
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    activatedTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    data: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    disableTouchRipple: PropTypes.bool,
    rippleDisplayCenter: PropTypes.bool,
    checked: PropTypes.bool,
    readOnly: PropTypes.bool,

    iconCls: PropTypes.string,
    rightIconCls: PropTypes.string,

    tip: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    itemRenderer: PropTypes.func,
    renderer: PropTypes.func,
    indeterminateCallback: PropTypes.func,

    autoSelect: PropTypes.bool,

    onClick: PropTypes.func,
    onSelect: PropTypes.func,
    onDeselect: PropTypes.func,
    onMouseEnter: PropTypes.func,
    onMouseLeave: PropTypes.func

};

ListItem.defaultProps = {

    index: 0,

    theme: Theme.DEFAULT,
    activatedTheme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    disabled: false,
    isLoading: false,
    disableTouchRipple: false,
    rippleDisplayCenter: false,
    checked: false,
    readOnly: false,

    tipPosition: Position.BOTTOM,

    checkboxUncheckedIconCls: 'far fa-square',
    checkboxCheckedIconCls: 'fas fa-check-square',
    checkboxIndeterminateIconCls: 'fas fa-minus-square',

    autoSelect: true

};

export default ListItem;
<commit_msg>update ListItem<commit_after>/**
 * @file ListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import Checkbox from '../Checkbox';
import Radio from '../Radio';
import CircularLoading from '../CircularLoading';
import TipProvider from '../TipProvider';
import TouchRipple from '../TouchRipple';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Position from '../_statics/Position';
import SelectMode from '../_statics/SelectMode';

class ListItem extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    multiSelectChangeHandler = checked => {

        const {onSelect, onDeselect} = this.props;

        if (checked) {
            onSelect && onSelect();
        } else {
            onDeselect && onDeselect();
        }

    };

    singleSelectChangeHandler = () => {
        if (!this.props.checked) {
            const {onSelect} = this.props;
            onSelect && onSelect();
        }
    };

    clickHandler = e => {

        const {disabled, isLoading, readOnly, autoSelect} = this.props;

        if (disabled || isLoading || readOnly) {
            return;
        }

        const {onClick} = this.props;
        onClick && onClick(e);

        if (!autoSelect) {
            return;
        }

        switch (this.props.selectMode) {
            case SelectMode.MULTI_SELECT:
                this.multiSelectChangeHandler(!this.props.checked);
                return;
            case SelectMode.SINGLE_SELECT:
                this.singleSelectChangeHandler();
                return;
        }

    };

    radioCheckHandler = () => {
        this.singleSelectChangeHandler();
    };

    checkboxCheckHandler = () => {
        this.multiSelectChangeHandler(true);
    };

    checkboxUncheckHandler = () => {
        this.multiSelectChangeHandler(false);
    };

    render() {

        const {

                index, className, style, theme, activatedTheme, data, text, desc, title, iconCls, rightIconCls,
                tip, tipPosition, disabled, isLoading, disableTouchRipple, rippleDisplayCenter, readOnly,
                renderer, itemRenderer,

                checked, selectTheme, selectMode, indeterminateCallback, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                onMouseEnter, onMouseLeave,
                parentEl

            } = this.props,

            indeterminated = indeterminateCallback && indeterminateCallback(data) || false,

            listItemClassName = classNames('list-item', {
                [`theme-${theme}`]: !checked && theme,
                [`theme-${activatedTheme}`]: checked && activatedTheme,
                activated: checked,
                indeterminated,
                [className]: className
            }),

            loadingIconPosition = (rightIconCls && !iconCls) ? 'right' : 'left';

        return (
            <TipProvider tipContent={tip}
                         parentEl={parentEl}
                         position={tipPosition}>

                    {
                        selectMode === SelectMode.SINGLE_SELECT && (radioUncheckedIconCls || radioCheckedIconCls) ?
                            <Radio className="list-item-select"
                                   theme={selectTheme}
                                   checked={checked}
                                   disabled={disabled || isLoading}
                                   uncheckedIconCls={radioUncheckedIconCls}
                                   checkedIconCls={radioCheckedIconCls}
                                   disableTouchRipple={true}
                                   onCheck={this.radioCheckHandler}/>
                            :
                            null
                    }

                    {
                        selectMode === SelectMode.MULTI_SELECT ?
                            <Checkbox className="list-item-select"
                                      theme={selectTheme}
                                      checked={checked}
                                      indeterminate={indeterminated}
                                      disabled={disabled || isLoading}
                                      uncheckedIconCls={checkboxUncheckedIconCls}
                                      checkedIconCls={checkboxCheckedIconCls}
                                      indeterminateIconCls={checkboxIndeterminateIconCls}
                                      disableTouchRipple={true}
                                      onCheck={this.checkboxCheckHandler}
                                      onUncheck={this.checkboxUncheckHandler}/>
                            :
                            null
                    }

                    {
                        isLoading && loadingIconPosition === 'left' ?

                                <CircularLoading className="button-loading-icon"
                                                 size="small"/>

                            :
                            (
                                iconCls ?
                                    <i className={`button-icon button-icon-left ${iconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        itemRenderer && typeof itemRenderer === 'function' ?
                            itemRenderer(data, index)
                            :
                            (
                                renderer && typeof renderer === 'function' ?
                                    renderer(data, index)
                                    :
                                    (
                                        desc ?

                                                    {text}

                                                    {desc}

                                            :
                                            text
                                    )
                            )
                    }

                    {
                        isLoading && loadingIconPosition === 'right' ?
                            <CircularLoading className="button-icon button-icon-right button-loading-icon"
                                             size="small"/>
                            :
                            (
                                rightIconCls ?
                                    <i className={`button-icon button-icon-right ${rightIconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        disableTouchRipple || readOnly ?
                            null
                            :
                            <TouchRipple ref="touchRipple"
                                         className={disabled || isLoading ? 'hidden' : ''}
                                         displayCenter={rippleDisplayCenter}/>
                    }

            </TipProvider>
        );

    }
}

ListItem.propTypes = {

    index: PropTypes.number,

    className: PropTypes.string,
    style: PropTypes.object,
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    activatedTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    data: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    disableTouchRipple: PropTypes.bool,
    rippleDisplayCenter: PropTypes.bool,
    checked: PropTypes.bool,
    readOnly: PropTypes.bool,

    iconCls: PropTypes.string,
    rightIconCls: PropTypes.string,

    tip: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    itemRenderer: PropTypes.func,
    renderer: PropTypes.func,
    indeterminateCallback: PropTypes.func,

    autoSelect: PropTypes.bool,

    onClick: PropTypes.func,
    onSelect: PropTypes.func,
    onDeselect: PropTypes.func,
    onMouseEnter: PropTypes.func,
    onMouseLeave: PropTypes.func

};

ListItem.defaultProps = {

    index: 0,

    theme: Theme.DEFAULT,
    activatedTheme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    disabled: false,
    isLoading: false,
    disableTouchRipple: false,
    rippleDisplayCenter: false,
    checked: false,
    readOnly: false,

    tipPosition: Position.BOTTOM,

    checkboxUncheckedIconCls: 'far fa-square',
    checkboxCheckedIconCls: 'fas fa-check-square',
    checkboxIndeterminateIconCls: 'fas fa-minus-square',

    autoSelect: true

};

export default ListItem;
<|endoftext|>


<commit_before>
'use strict';

// Lists controller
angular.module('admin').controller('AdminController', ['$scope', '$http', 'Authentication', '$stateParams', '$location', '$modal', '$log',
  function($scope, $http, Authentication, $stateParams, $location, $modal, $log) {

    $scope.user = Authentication.user;

    $scope.weeks = 0;
    $scope.camp_options = [];
    $scope.formData = {};
    $scope.data = {};

    // Create new user
    $scope.create = function(role) {
        if (role === 'admin'){
            $scope.credentials.role = 'admin';
        }
        if (role === 'inst'){
            $scope.credentials.role = 'instructor';
        }
        $http.post('/admin/create', $scope.credentials).success(function(response) {
            // If successful show success message and clear form
            $scope.success = true;
            $scope.passwordDetails = null;
            if (response.role === 'instructor') {
                $location.path('/admin/instrs');
            }
            else{
                $location.path('/admins');
            }
        }).error(function(response) {
            $scope.error = response.message;
        });
    };

    $scope.viewTrainees = function() {
        $http.get('/admin/trainees').success(function(response) {
          // If successful show success message and clear form
            $scope.role = [];
            $scope.success = true;
            $scope.trainees = response;

        }).error(function(response) {
            $scope.error = response.message;
        });
    };

    $scope.listApplicants = function() {
        $http.get('/admin/applicants').success(function(response) {
          // If successful show success message and clear form
            $scope.success = true;
            $scope.applicants = response;

        }).error(function(response) {
            $scope.error = response.message;
        });
    };

    $scope.viewApplicant = function() {
      $http.get('/admin/appt/' + $stateParams.apptId).success(function(response) {
        // If successful show success message and clear form
        $scope.data = {};
        $scope.success = true;
        $scope.appt = response;
        $scope.skillSet = response.skillSet;

        $scope.currPlacementEditorEnabled = false;
        $scope.editableCurrCompany = '';
        $scope.startDateEditorEnabled = false;
        $scope.endDateEditorEnabled = false;
        $scope.editableStartDate = '';
        $scope.editableEndDate = '';
        $scope.editableName = '';
        $scope.email = '';

        $scope.skillNameEditorEnabled = [];
        $scope.skillScoreEditorEnabled = [];
        $scope.editableSkillName = [];
        $scope.editableSkillScore = [];
        $score.editableDetails = [];

        for (var i in $scope.appt.skillSet){
          $scope.skillNameEditorEnabled[i] = false;
          $scope.skillScoreEditorEnabled[i] = false;
          $scope.editableSkillName[i] = '';
          $scope.editableSkillScore[i] = 1;
        }

        $scope.enableNameEditor = function(field) {
          if (field === 'name') {};
        };

        $scope.enableCurrPlacementEditor = function(field) {
          if (field === 'company'){
            $scope.currPlacementEditorEnabled = true;
            $scope.editableCurrCompany = $scope.appt.currPlacement.status;
          }
          if (field === 'startDate'){
            $scope.startDateEditorEnabled = true;
            $scope.editableStartDate = '';
          }
          if (field === 'endDate'){
            $scope.endDateEditorEnabled = true;
            $scope.editableEndDate = '';
          }
        };

        $scope.enableskillEditor = function(field, index) {
          if (field === 'skillName'){
            $scope.skillNameEditorEnabled[index] = true;
            $scope.editableSkillName[index] = $scope.appt.skillSets[index].skill;
          }
          if (field === 'skillScore'){
            $scope.skillScoreEditorEnabled[index] = true;
            $scope.editableSkillScore[index] = $scope.appt.skillSets[index].rating;
          }
        };

        $scope.disableskillEditor = function(field, index) {
          if (field === 'skillName'){
            $scope.skillNameEditorEnabled[index] = false;
          }
          if (field === 'skillScore'){
            $scope.skillScoreEditorEnabled[index] = false;
          }
        };

        $scope.disableCurrPlacementEditor = function(field, index) {
          if (field === 'company'){
            $scope.currPlacementEditorEnabled = false;
          }
          if (field === 'startDate'){
            $scope.startDateEditorEnabled = false;
          }
          if (field === 'endDate'){
            $scope.endDateEditorEnabled = false;
          }
        };

        $scope.saveskill = function(field, index) {
          if (field === 'skillName'){
            $scope.appt.skillSets[index].skill = $scope.editableSkillName[index];
            $scope.updateSkill($scope.appt, index);
          }
          if (field === 'skillScore'){
            $scope.appt.skillSets[index].rating = $scope.editableSkillScore[index];
            $scope.updateSkill($scope.appt, index);
          }
          $scope.disableskillEditor(field, index);
        };

        $scope.saveCurrPlacement = function(field, index) {
          if (field === 'company'){
            $scope.appt.currPlacement.status = $scope.editableCurrCompany;
            $scope.updatePlacement($scope.appt);
            $scope.disableCurrPlacementEditor('company');
          }
          if (field === 'startDate'){
            $scope.appt.currPlacement.startDate = $scope.editableStartDate;
            $scope.updatePlacement($scope.appt);
            $scope.disableCurrPlacementEditor('startDate');
          }
          if (field === 'endDate'){
            $scope.appt.currPlacement.endDate = $scope.editableEndDate;
            $scope.updatePlacement($scope.appt);
            $scope.disableCurrPlacementEditor('endDate');
          }
        };
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.updateSkill = function(appt, index) {
      $http.put('/admin/trainee/' + appt._id + '/rate/' + appt.skillSets[index]._id, appt.skillSets[index]).success(function(response) {
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.updatePlacement = function(appt) {
      $http.put('/admin/fellow/' + appt._id + '/placement', appt.currPlacement).success(function(response) {
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.deleteUser = function(userId, index) {
      $scope.camp.applicants.splice(index, 1);

      $http.put('/admin/camp/' + $scope.camp._id, $scope.camp).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });

      $http.delete('/admin/user/' + userId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.deleteAdmin = function(userId, index) {
      $scope.admins.splice(index, 1);

      $http.delete('/admin/user/' + userId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.deleteInstr = function(userId, index) {
      $scope.instructors.splice(index, 1);

      $http.delete('/admin/user/' + userId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.deleteFellow = function(userId, index) {
      $scope.fellows.splice(index, 1);

      $http.delete('/admin/user/' + userId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.getSkillLevel = function(val){
      val = parseInt(val);
      if(val <= 2){
        return 'beginner';
      }
      else if(val <= 4){
        return 'junior';
      }
      else if(val <= 6){
        return 'intermediate';
      }
      else if(val <= 8){
        return 'senior';
      }
      else{
        return 'expert';
      }
    };

    $scope.listFellows = function() {
      $http.get('/admin/fellows').success(function(response) {
        // If successful show success message and clear form
        $scope.fellows = response;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.IsFellowUnavailable = function(fellow) {
      var weeks = parseInt($scope.weeks);
      var date = moment().add(weeks, 'weeks');
      if(!fellow.placements){
        return true;
      }
      if((fellow.placements && fellow.placements.length === 0) || weeks === 0){
        return true;
      }
      else if(moment(fellow.placements[0].end_date) > date){
        return false;
      }
      else{
        return true;
      }
    };

    $scope.get_availability_date = function(fellow){
      if((fellow.placements && fellow.placements.length > 0)){
        if(moment(fellow.placements[0].end_date) > moment()){
          return moment(fellow.placements[0].end_date).format("LL");
        }
        else{
          return "Now";
        }
      }
      else{
        return "Now";
      }
    };

    $scope.get_fellow_work_days = function(fellow) {
      var oneday = 24*3600*1000;
      if ((fellow.placements && fellow.placements.length > 0)) {
        var curr_date = new Date();
        var fellowavailabilityweeks = Math.ceil(new Date(fellow.placements[0].end_date).getTime() - curr_date.getTime())/(oneday *7);
        if (fellowavailabilityweeks <= 0) {
          return 0;
        } else {
          return Math.ceil(fellowavailabilityweeks);
        }
      }
      else{
        return 0;
      }
    }
    $scope.check_placement = function(placement, index){
        if ($scope.get_fellow_work_days(placement) <= 0) {
          $scope.fellows[index].available = 'Needs Work';
          $scope.fellows[index].week = $scope.get_fellow_work_days(placement) ;
        }
        else{
            $scope.fellows[index].available = 'Currently Placed';
            $scope.fellows[index].week = $scope.get_fellow_work_days(placement);
        }
    };

    $scope.listInstructors = function() {
      $http.get('/admin/instructors').success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.listAdmins = function() {
      $http.get('/admin/admins').success(function(response) {
        // If successful show success message and clear form
        $scope.admins = response;
        $scope.success = true;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.listInstructors = function() {
      $http.get('/admin/instructors').success(function(response) {
        // If successful show success message and clear form
        $scope.instructors = response;
        $scope.success = true;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.changeStatus = function() {
      $http.put('/admin/appt/' + $stateParams.apptId, $scope.data).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;
        $location.path('/admin/camps/' + $stateParams.bootcampId);

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.changeRoleToFellow = function(trainee_id, index) {
      $scope.trainees.splice(index, 1);
      $http.put('/admin/appt/' + trainee_id + '/role', {role: $scope.role[index]}).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.viewInstructor = function(instrId) {
      $http.get('/admin/appt/' + instrId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.rateFellow = function(traineeId, skillId, newRating) {
      var url = '/admin/trainee/' + traineeId + '/skills/' + skillId;
      $http.put(url, {rating: newRating}).success(function(response) {
        $scope.success = true;
        $scope.appt = response;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.addPlacement = function() {
      $http.post('/admin/fellow/' + $stateParams.apptId + '/placements', $scope.data).success(function(response) {

        // If successful show success message and clear form
        $scope.data.company = '';
        $scope.data.jobDescription ='' ;
        $scope.data.start_date = '';
        $scope.data.end_date = '';

        $scope.appt = response;
      }).error(function(response) {
        $scope.error = response.message;
        if ($scope.error.type === 'date'){
          $scope.error = "Please follow the date pattern specified M/D/YY e.g (use 2/5/2014 for 5th Feb 2014)"
        }
      });
    };

    /*SKILLS*/
    $scope.listSkills = function() {
      $http.get('/admin/skills').success(function(response) {
        $scope.skills = response;

      }).error(function(response) {
         $scope.error = response.message;
         $location.path('/admin/welcome');

      });
    };

    $scope.openSkillModal = function (size) {

        var modalInstance = $modal.open({
            templateUrl: '/modules/admin/views/admin.skills_modal.html',
            controller: 'ModalInstanceCtrl',
            size: size,
            resolve: {
                categories: function () {
                    return $scope.skillCategories;
                }
            }
        });

        modalInstance.result.then(function (selectedItem) {
            $scope.selected = selectedItem;
        }, function () {
            $log.info('Modal dismissed at: ' + new Date());
        });
    };

    $scope.getSkillCategories = function(){
        $http.get('/admin/skillCategories').success(function(response) {
            $scope.skillCategories = response;

        }).error(function(response) {
            $scope.error = response.message;
            $location.path('/admin/welcome');

        });
    };

    /*DELETE CATEGORY*/
    $scope.deleteSkillCategory = function(catId, index) {
      $scope.skills.splice(index, 1);
      $http.delete('/admin/skillCategories/' + catId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

  }

]);
<commit_msg>Change appt to applicant<commit_after>
'use strict';

// Lists controller
angular.module('admin').controller('AdminController', ['$scope', '$http', 'Authentication', '$stateParams', '$location', '$modal', '$log',
  function($scope, $http, Authentication, $stateParams, $location, $modal, $log) {

    $scope.user = Authentication.user;

    $scope.weeks = 0;
    $scope.camp_options = [];
    $scope.formData = {};
    $scope.data = {};

    // Create new user
    $scope.create = function(role) {
        if (role === 'admin'){
            $scope.credentials.role = 'admin';
        }
        if (role === 'inst'){
            $scope.credentials.role = 'instructor';
        }
        $http.post('/admin/create', $scope.credentials).success(function(response) {
            // If successful show success message and clear form
            $scope.success = true;
            $scope.passwordDetails = null;
            if (response.role === 'instructor') {
                $location.path('/admin/instrs');
            }
            else{
                $location.path('/admins');
            }
        }).error(function(response) {
            $scope.error = response.message;
        });
    };

    $scope.viewTrainees = function() {
        $http.get('/admin/trainees').success(function(response) {
          // If successful show success message and clear form
            $scope.role = [];
            $scope.success = true;
            $scope.trainees = response;

        }).error(function(response) {
            $scope.error = response.message;
        });
    };

    $scope.listApplicants = function() {
        $http.get('/admin/applicants').success(function(response) {
          // If successful show success message and clear form
            $scope.success = true;
            $scope.applicants = response;

        }).error(function(response) {
            $scope.error = response.message;
        });
    };

    $scope.viewApplicant = function() {
      $http.get('/admin/applicant/' + $stateParams.apptId).success(function(response) {
        // If successful show success message and clear form
        $scope.data = {};
        $scope.success = true;
        $scope.appt = response;
        $scope.skillSet = response.skillSet;

        $scope.currPlacementEditorEnabled = false;
        $scope.editableCurrCompany = '';
        $scope.startDateEditorEnabled = false;
        $scope.endDateEditorEnabled = false;
        $scope.editableStartDate = '';
        $scope.editableEndDate = '';
        $scope.editableName = '';
        $scope.email = '';

        $scope.skillNameEditorEnabled = [];
        $scope.skillScoreEditorEnabled = [];
        $scope.editableSkillName = [];
        $scope.editableSkillScore = [];
        $scope.editableDetails = [];

        for (var i in $scope.appt.skillSet){
          $scope.skillNameEditorEnabled[i] = false;
          $scope.skillScoreEditorEnabled[i] = false;
          $scope.editableSkillName[i] = '';
          $scope.editableSkillScore[i] = 1;
        }

        $scope.enableNameEditor = function(field) {
          if (field === 'name') {};
        };

        $scope.enableCurrPlacementEditor = function(field) {
          if (field === 'company'){
            $scope.currPlacementEditorEnabled = true;
            $scope.editableCurrCompany = $scope.appt.currPlacement.status;
          }
          if (field === 'startDate'){
            $scope.startDateEditorEnabled = true;
            $scope.editableStartDate = '';
          }
          if (field === 'endDate'){
            $scope.endDateEditorEnabled = true;
            $scope.editableEndDate = '';
          }
        };

        $scope.enableskillEditor = function(field, index) {
          if (field === 'skillName'){
            $scope.skillNameEditorEnabled[index] = true;
            $scope.editableSkillName[index] = $scope.appt.skillSets[index].skill;
          }
          if (field === 'skillScore'){
            $scope.skillScoreEditorEnabled[index] = true;
            $scope.editableSkillScore[index] = $scope.appt.skillSets[index].rating;
          }
        };

        $scope.disableskillEditor = function(field, index) {
          if (field === 'skillName'){
            $scope.skillNameEditorEnabled[index] = false;
          }
          if (field === 'skillScore'){
            $scope.skillScoreEditorEnabled[index] = false;
          }
        };

        $scope.disableCurrPlacementEditor = function(field, index) {
          if (field === 'company'){
            $scope.currPlacementEditorEnabled = false;
          }
          if (field === 'startDate'){
            $scope.startDateEditorEnabled = false;
          }
          if (field === 'endDate'){
            $scope.endDateEditorEnabled = false;
          }
        };

        $scope.saveskill = function(field, index) {
          if (field === 'skillName'){
            $scope.appt.skillSets[index].skill = $scope.editableSkillName[index];
            $scope.updateSkill($scope.appt, index);
          }
          if (field === 'skillScore'){
            $scope.appt.skillSets[index].rating = $scope.editableSkillScore[index];
            $scope.updateSkill($scope.appt, index);
          }
          $scope.disableskillEditor(field, index);
        };

        $scope.saveCurrPlacement = function(field, index) {
          if (field === 'company'){
            $scope.appt.currPlacement.status = $scope.editableCurrCompany;
            $scope.updatePlacement($scope.appt);
            $scope.disableCurrPlacementEditor('company');
          }
          if (field === 'startDate'){
            $scope.appt.currPlacement.startDate = $scope.editableStartDate;
            $scope.updatePlacement($scope.appt);
            $scope.disableCurrPlacementEditor('startDate');
          }
          if (field === 'endDate'){
            $scope.appt.currPlacement.endDate = $scope.editableEndDate;
            $scope.updatePlacement($scope.appt);
            $scope.disableCurrPlacementEditor('endDate');
          }
        };
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.updateSkill = function(appt, index) {
      $http.put('/admin/trainee/' + appt._id + '/rate/' + appt.skillSets[index]._id, appt.skillSets[index]).success(function(response) {
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.updatePlacement = function(appt) {
      $http.put('/admin/fellow/' + appt._id + '/placement', appt.currPlacement).success(function(response) {
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.deleteUser = function(userId, index) {
      $scope.camp.applicants.splice(index, 1);

      $http.put('/admin/camp/' + $scope.camp._id, $scope.camp).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });

      $http.delete('/admin/user/' + userId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.deleteAdmin = function(userId, index) {
      $scope.admins.splice(index, 1);

      $http.delete('/admin/user/' + userId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.deleteInstr = function(userId, index) {
      $scope.instructors.splice(index, 1);

      $http.delete('/admin/user/' + userId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.deleteFellow = function(userId, index) {
      $scope.fellows.splice(index, 1);

      $http.delete('/admin/user/' + userId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.getSkillLevel = function(val){
      val = parseInt(val);
      if(val <= 2){
        return 'beginner';
      }
      else if(val <= 4){
        return 'junior';
      }
      else if(val <= 6){
        return 'intermediate';
      }
      else if(val <= 8){
        return 'senior';
      }
      else{
        return 'expert';
      }
    };

    $scope.listFellows = function() {
      $http.get('/admin/fellows').success(function(response) {
        // If successful show success message and clear form
        $scope.fellows = response;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.IsFellowUnavailable = function(fellow) {
      var weeks = parseInt($scope.weeks);
      var date = moment().add(weeks, 'weeks');
      if(!fellow.placements){
        return true;
      }
      if((fellow.placements && fellow.placements.length === 0) || weeks === 0){
        return true;
      }
      else if(moment(fellow.placements[0].end_date) > date){
        return false;
      }
      else{
        return true;
      }
    };

    $scope.get_availability_date = function(fellow){
      if((fellow.placements && fellow.placements.length > 0)){
        if(moment(fellow.placements[0].end_date) > moment()){
          return moment(fellow.placements[0].end_date).format("LL");
        }
        else{
          return "Now";
        }
      }
      else{
        return "Now";
      }
    };

    $scope.get_fellow_work_days = function(fellow) {
      var oneday = 24*3600*1000;
      if ((fellow.placements && fellow.placements.length > 0)) {
        var curr_date = new Date();
        var fellowavailabilityweeks = Math.ceil(new Date(fellow.placements[0].end_date).getTime() - curr_date.getTime())/(oneday *7);
        if (fellowavailabilityweeks <= 0) {
          return 0;
        } else {
          return Math.ceil(fellowavailabilityweeks);
        }
      }
      else{
        return 0;
      }
    }
    $scope.check_placement = function(placement, index){
        if ($scope.get_fellow_work_days(placement) <= 0) {
          $scope.fellows[index].available = 'Needs Work';
          $scope.fellows[index].week = $scope.get_fellow_work_days(placement) ;
        }
        else{
            $scope.fellows[index].available = 'Currently Placed';
            $scope.fellows[index].week = $scope.get_fellow_work_days(placement);
        }
    };

    $scope.listInstructors = function() {
      $http.get('/admin/instructors').success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.listAdmins = function() {
      $http.get('/admin/admins').success(function(response) {
        // If successful show success message and clear form
        $scope.admins = response;
        $scope.success = true;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.listInstructors = function() {
      $http.get('/admin/instructors').success(function(response) {
        // If successful show success message and clear form
        $scope.instructors = response;
        $scope.success = true;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.changeStatus = function() {
      $http.put('/admin/applicant/' + $stateParams.apptId, $scope.data).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;
        $location.path('/admin/camps/' + $stateParams.bootcampId);

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.changeRoleToFellow = function(trainee_id, index) {
      $scope.trainees.splice(index, 1);
      $http.put('/admin/applicant/' + trainee_id + '/role', {role: $scope.role[index]}).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;
      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.viewInstructor = function(instrId) {
      $http.get('/admin/applicant/' + instrId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.rateFellow = function(traineeId, skillId, newRating) {
      var url = '/admin/trainee/' + traineeId + '/skills/' + skillId;
      $http.put(url, {rating: newRating}).success(function(response) {
        $scope.success = true;
        $scope.appt = response;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

    $scope.addPlacement = function() {
      $http.post('/admin/fellow/' + $stateParams.apptId + '/placements', $scope.data).success(function(response) {

        // If successful show success message and clear form
        $scope.data.company = '';
        $scope.data.jobDescription ='' ;
        $scope.data.start_date = '';
        $scope.data.end_date = '';

        $scope.appt = response;
      }).error(function(response) {
        $scope.error = response.message;
        if ($scope.error.type === 'date'){
          $scope.error = "Please follow the date pattern specified M/D/YY e.g (use 2/5/2014 for 5th Feb 2014)"
        }
      });
    };

    /*SKILLS*/
    $scope.listSkills = function() {
      $http.get('/admin/skills').success(function(response) {
        $scope.skills = response;

      }).error(function(response) {
         $scope.error = response.message;
         $location.path('/admin/welcome');

      });
    };

    $scope.openSkillModal = function (size) {

        var modalInstance = $modal.open({
            templateUrl: '/modules/admin/views/admin.skills_modal.html',
            controller: 'ModalInstanceCtrl',
            size: size,
            resolve: {
                categories: function () {
                    return $scope.skillCategories;
                }
            }
        });

        modalInstance.result.then(function (selectedItem) {
            $scope.selected = selectedItem;
        }, function () {
            $log.info('Modal dismissed at: ' + new Date());
        });
    };

    $scope.getSkillCategories = function(){
        $http.get('/admin/skillCategories').success(function(response) {
            $scope.skillCategories = response;

        }).error(function(response) {
            $scope.error = response.message;
            $location.path('/admin/welcome');

        });
    };

    /*DELETE CATEGORY*/
    $scope.deleteSkillCategory = function(catId, index) {
      $scope.skills.splice(index, 1);
      $http.delete('/admin/skillCategories/' + catId).success(function(response) {
        // If successful show success message and clear form
        $scope.success = true;

      }).error(function(response) {
        $scope.error = response.message;
      });
    };

  }

]);
<|endoftext|>


<commit_before>import Events from "../Utils/Events"
import { configure, addParameters } from "@storybook/react"
import { createMediaStyle } from "Utils/Responsive"

const req = require.context("../", true, /\.story\.tsx$/)

function loadStories() {
  req.keys().forEach(filename => {
    return req(filename)
  })
}

// Add RRM styles to Storybooks head
const rrmStyle = document.createElement("style")
rrmStyle.innerHTML = createMediaStyle()
document.head.appendChild(rrmStyle)

addParameters({
  inline: true,
  brandTitle: "Reaction",
  showAddonPanel: false,
  sortStoriesByKind: true,
})

setTimeout(() => {
  configure(loadStories, module)
})

Events.onEvent(data => {
  console.log("Tracked event", data)
})

if (!window.sd || !(typeof window.sd === "object")) {
  window.sd = {}
}
<commit_msg>Provides the theme<commit_after>import React from "react"
import Events from "../Utils/Events"
import { configure, addParameters, addDecorator } from "@storybook/react"
import { createMediaStyle } from "Utils/Responsive"
import { Theme } from "@artsy/palette"

const req = require.context("../", true, /\.story\.tsx$/)

function loadStories() {
  req.keys().forEach(filename => {
    return req(filename)
  })
}

// Add RRM styles to Storybooks head
const rrmStyle = document.createElement("style")
rrmStyle.innerHTML = createMediaStyle()
document.head.appendChild(rrmStyle)

addParameters({
  inline: true,
  brandTitle: "Reaction",
  showAddonPanel: false,
  sortStoriesByKind: true,
})

addDecorator(storyFn => <Theme>{storyFn()}</Theme>)

setTimeout(() => {
  configure(loadStories, module)
})

Events.onEvent(data => {
  console.log("Tracked event", data)
})

if (!window.sd || !(typeof window.sd === "object")) {
  window.sd = {}
}
<|endoftext|>


<commit_before>'use strict';

angular.module('admin').controller('LoginController', ['$scope', 'AuthService', '$location',
    function($scope, AuthService, $location) {

        $scope.login = function(credentials)
        {
            AuthService.login(credentials).then(
                function (user)
                {
                    $location.path('/admin/index');
                },
                function()
                {
                    $scope.invalid = true;
                });

        };

//        $scope.authentication = Authentication;
//
//        // If user is signed in then redirect back home
//        if ($scope.authentication.user) $location.path('/');
//
//        $scope.signup = function() {
//            $http.post('/auth/signup', $scope.credentials).success(function(response) {
//                // If successful we assign the response to the global user model
//                $scope.authentication.user = response;
//
//                // And redirect to the index page
//                $location.path('/');
//            }).error(function(response) {
//                $scope.error = response.message;
//            });
//        };
//
//        $scope.signin = function() {
//            $http.post('/auth/signin', $scope.credentials).success(function(response) {
//                // If successful we assign the response to the global user model
//                $scope.authentication.user = response;
//
//                // And redirect to the index page
//                $location.path('/');
//            }).error(function(response) {
//                $scope.error = response.message;
//            });
//        };
    }
]);<commit_msg>remove old code<commit_after>'use strict';

angular.module('admin').controller('LoginController', ['$scope', 'AuthService', '$location',
    function($scope, AuthService, $location) {

        $scope.login = function(credentials)
        {
            AuthService.login(credentials).then(
                function (user)
                {
                    $location.path('/admin/index');
                },
                function()
                {
                    $scope.invalid = true;
                });
        };
    }
]);<|endoftext|>


<commit_before>'use strict';

angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus','toastr', 'PusherService','$translate','$location',
	function($scope, Authentication, Menus, toastr, PusherService, $translate,$location) {

		$scope.authentication = Authentication;
		$scope.isCollapsed = false;
		$scope.menu = Menus.getMenu('topbar');
		$scope.imgPath = Authentication.user._id + '.png';
		$scope.isActive = true;
		$scope.imgPath = 'https://s3.amazonaws.com/sumacrm/avatars/' + Authentication.user._id;
		$scope.toggleCollapsibleMenu = function() {
			$scope.isCollapsed = !$scope.isCollapsed;
		};
		$scope.changeLanguage = function (langKey) {
			$translate.use(langKey);
			$scope.isActive = !$scope.isActive;
		};
		// Collapsing the menu after navigation
		$scope.$on('$stateChangeSuccess', function() {
			if (!$scope.authentication.user ) {
				if($location.path() === '/signup') {
					$location.path('/signup');
				}
				else{
					$location.path('/#!');
				}
			}
			$scope.isCollapsed = false;
			$scope.imgPath = 'https://s3.amazonaws.com/sumacrm/avatars/' + Authentication.user._id;
		});

		PusherService.listen('Pusher-channel','Pusher-event', function(err, data) {
			toastr.success(data.message);
		});
	}
]);
<commit_msg>JsLint.<commit_after>'use strict';

angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus','toastr', 'PusherService','$translate','$location',
	function($scope, Authentication, Menus, toastr, PusherService, $translate, $location) {

		$scope.authentication = Authentication;
		$scope.isCollapsed = false;
		$scope.menu = Menus.getMenu('topbar');
		$scope.imgPath = Authentication.user._id + '.png';
		$scope.isActive = true;
		$scope.imgPath = 'https://s3.amazonaws.com/sumacrm/avatars/' + Authentication.user._id;
		$scope.toggleCollapsibleMenu = function() {
			$scope.isCollapsed = !$scope.isCollapsed;
		};
		$scope.changeLanguage = function (langKey) {
			$translate.use(langKey);
			$scope.isActive = !$scope.isActive;
		};
		// Collapsing the menu after navigation
		$scope.$on('$stateChangeSuccess', function() {
			if (!$scope.authentication.user ) {
				if ($location.path() === '/signup') {
					$location.path('/signup');
				}
				else {
					$location.path('/#!');
				}
			}
			$scope.isCollapsed = false;
			$scope.imgPath = 'https://s3.amazonaws.com/sumacrm/avatars/' + Authentication.user._id;
		});

		PusherService.listen('Pusher-channel','Pusher-event', function(err, data) {
			toastr.success(data.message);
		});
	}
]);
<|endoftext|>


<commit_before>
//helpers
import sequence from '../../tests/_helpers/sequence';
import report_result_summary from '../../tests/_helpers/report_result_summary';

//tests
import max from '../../tests/max/max';
import min from '../../tests/min/min';
import inside from '../../tests/inside/inside';

window.onload = function(){
	sequence([
		// max(),
		// min(),
		inside(),
		report_result_summary(),
	])
}
<commit_msg>re-enabled min/max tests<commit_after>
//helpers
import sequence from '../../tests/_helpers/sequence';
import report_result_summary from '../../tests/_helpers/report_result_summary';

//tests
import max from '../../tests/max/max';
import min from '../../tests/min/min';
import inside from '../../tests/inside/inside';

window.onload = function(){
	sequence([
		max(),
		min(),
		inside(),
		report_result_summary(),
	])
}
<|endoftext|>


<commit_before>// Config
var config = require('../config');

// Requirements
var _ = require('lodash');
var async = require('async');
var chalk = require('chalk');
var cordiality = +config.get('cordiality');
var fs = require('fs');
var Organization = require('../Organization');
var path = require('path');
var Promise = require('bluebird');
var Submissions = require('../Submissions');

// Promises
var stringify = Promise.promisify(require('csv-stringify'));

// Modifications
Organization.prototype.hasSubmissionAvailable = function hasSubmissionAvailable() {
    var indexesToRemove = [];
    var submission = _.find(this.eligible, (submission, index) => {
        if (!this.hashes[submission.hash] && submission.givenCount < cordiality) {
            return true;
        } else {
            indexesToRemove.push(index);
            return false;
        }
    });

    if (indexesToRemove.length > 0) {
        _.pullAt(this.eligible, indexesToRemove);
    }

    return !!submission;
}

Organization.prototype.takeSubmission = function takeSubmission() {
    var submission = this.eligible.shift();
    submission.givenCount++;
    this.hashes[submission.hash] = true;
    this.received.push(submission);
}

// Class
module.exports = class Olympic {

    constructor(params) {
        this.callback = params.callback;
        this.organizations = params.organizations;
        this.submissions = params.submissions;
        this.start();
    }

    start() {
        Promise.resolve('everything')
        .then(f => {
            // Modifying organization and submissions objects
            _.each(this.organizations, organization => {
                organization.eligible = [];
                organization.eligibleCount = 0;
                organization.received = [];
                organization.source = organization.sources[0];
                organization.sourced = [];
                organization.states = organization.states || [];
                organization.swappableSourced = [];
            });

            _.each(this.submissions.hashes, submission => {
                // Used to determine rarity of submission
                submission.eligible = [];
                submission.givenCount = 0;
            });
        })

        // Assigning submissions to organizations
        .then(f => {
            var sourceMap = {};

            _.each(this.organizations, organization => {
                _.each(organization.sources, source => {
                    sourceMap[source] = organization;
                });
            });

            _.each(this.submissions.hashes, submission => {
                submission.sourceObjects = [];

                _.each(_.keys(submission.sources), source => {
                    var organization = sourceMap[source];
                    if (organization) {
                        organization.hashes[submission.hash] = true;
                        organization.sourced.push(submission);
                        submission.sourceObjects.push(organization);

                        if (submission.swappable) {
                            organization.swappableSourced.push(submission);
                        }
                    }
                });
            });
        })

        // Remove duplicates
        .then(f => {
            _.each(this.submissions.hashes, submission => {
                submission.sourceObjects = _.uniq(submission.sourceObjects);
            });

            _.each(this.organizations, organization => {
                organization.sourced = _.uniq(organization.sourced);
                organization.swappableSourced = _.uniq(organization.swappableSourced);
            });
        })

        // Saving sourced signatures
        .then(f => this.organizations)
        .each(organization => {
            var sourced = _.map(organization.sourced, submission => submission.row);
            return stringify(sourced, {
                header: true,
                quoted: true,
            }).then(csv => {
                var filename = path.join(__dirname, `../../output/${organization.source}-own.csv`);
                fs.writeFileSync(filename, csv);
            });
        })

        // Removing organizations that aren't swapping
        .then(f => {
            this.organizations = _.filter(this.organizations, organization => organization.swapping);
        })

        // Assigning eligible organizations to each submission
        .then(f => {
            // Assigning eligiblity to submissions
            _.each(this.submissions.swappableHashes, submission => {
                _.each(this.organizations, organization => {
                    // Skip suppressed hashes
                    if (organization.hashes[submission.hash]) {
                        return;
                    }

                    // Used to determine rarity of submission
                    submission.eligible.push(organization);
                });
            });
        })

        // Sort submissions by eligibility
        // Most common to most rare
        .then(f => {
            this.submissions.swappableHashes.sort((a, b) => a.eligible.length - b.eligible.length);
        })

        // Assigning eligible submissions to organizations
        .then(f => {
            _.each(this.submissions.swappableHashes, submission => {
                _.each(submission.eligible, organization => {
                    organization.eligible.push(submission);
                });
            });
        })

        // Modifying eligibility based on organizational state preference
        .then(f => {
            _.each(this.organizations, organization => {
                if (!organization.states || organization.states.length === 0) {
                    return;
                }

                organization.eligible.sort((submissionA, submissionB) => {
                    var inStateA = +_.includes(organization.states, submissionA.row.state);
                    var inStateB = +_.includes(organization.states, submissionB.row.state);
                    return inStateB - inStateA;
                });
            });
        })

        // Show amount of eligible hashes per organization
        .then(f => {
            console.log(`Eligible hashes for...`);
            _.each(this.organizations, organization => {
                var eligible = {};
                var eligibleInState = {};

                _.each(organization.eligible, submission => {
                    eligible[submission.hash] = true;

                    if (organization.states.length > 0 && _.includes(organization.states, submission.row.state)) {
                        eligibleInState[submission.hash] = true;
                    }
                });

                organization.eligibleCount = _.size(eligible);
                organization.eligibleInStateCount = _.size(eligibleInState);

                var message = `- ${chalk.yellow(organization.name)}:\n`;

                if (organization.states.length) {
                    message += `    Geolocated: ${chalk.green(organization.eligibleInStateCount)}\n`;
                }

                message += `    Total: ${chalk.green(organization.eligibleCount)}`;

                console.log(message);
            });
        })

        // Swap
        .then(f => {
            var finish;
            var promise = new Promise((resolve, reject) => {
                finish = resolve;
            });

            var organizations = this.getSortedListOfOwedOrganizations();

            async.forever(next => {
                // Check if every organization has bounced
                if (_.isEmpty(organizations)) {
                    next(1);
                    return;
                }

                // Find the organization who is the farthest from matching their sourced count
                var organization = _.maxBy(
                    organizations,
                    // organization => organization.swappableSourced.length / organization.eligibleCount // Ratio
                    organization => organization.swappableSourced.length - organization.received.length // Sum
                );

                if (organization.received.length === organization.swappableSourced.length) {
                    _.pull(organizations, organization);
                    next();
                    return;
                }

                var submission = organization.hasSubmissionAvailable();

                if (!submission) {
                    _.pull(organizations, organization);
                    next();
                    return;
                }

                organization.takeSubmission();

                next();
            }, err => {
                finish();
            });

            return promise;
        })

        // Save summary
        .then(f => {
            var filename = path.join(__dirname, `../../output/summary.csv`);
            var summary = 'Organization,Sourced,Eligible,Received,Percent\n';
            _.each(this.organizations, organization => {
                var eligibleCount = organization.eligibleCount;
                var name = organization.name;
                var receivedTotal = organization.received.length;
                var sourced = organization.swappableSourced.length;
                var percent = ((receivedTotal / sourced) * 100).toFixed(2);
                summary += `${name},${sourced},${eligibleCount},${receivedTotal},${percent}%\n`;
            });
            fs.writeFileSync(filename, summary);
        })

        // Save organization files
        .then(f => this.organizations)
        .each(organization => {
            var received = _.map(organization.received, submission => submission.row);
            return stringify(received, {
                header: true,
                quoted: true,
            }).then(csv => {
                var filename = path.join(__dirname, `../../output/${organization.source}-swapped.csv`);
                fs.writeFileSync(filename, csv);
            });
        })

        // Finish
        .then(f => {
            var count = _.sumBy(this.organizations, organization => {
                return organization.received.length;
            });
            console.log(`Finished. In total, ${chalk.green(count)} names were swapped.`);
        });
    }

    getSortedListOfOwedOrganizations() {
        return _.filter(this.organizations, organization => {
            return (
                organization.swappableSourced.length > organization.received.length
            );
        }).sort((a, b) => {
            // Sort those who've sourced the most first
            return b.swappableSourced.length - a.swappableSourced.length;
        });
    }
}
<commit_msg>Sorting organizations based on sourced/eligible ratio. Sorting names by swap count, state preference, and rarity.<commit_after>// Config
var config = require('../config');

// Requirements
var _ = require('lodash');
var async = require('async');
var chalk = require('chalk');
var cordiality = +config.get('cordiality');
var fs = require('fs');
var Organization = require('../Organization');
var path = require('path');
var Promise = require('bluebird');
var Submissions = require('../Submissions');

// Promises
var stringify = Promise.promisify(require('csv-stringify'));

// Modifications
Organization.prototype.hasSubmissionAvailable = function hasSubmissionAvailable() {
    var indexesToRemove = [];
    var submission = _.find(this.eligible, (submission, index) => {
        if (!this.hashes[submission.hash] && submission.givenCount < cordiality) {
            return true;
        } else {
            indexesToRemove.push(index);
            return false;
        }
    });

    if (indexesToRemove.length > 0) {
        _.pullAt(this.eligible, indexesToRemove);
    }

    return !!submission;
}

Organization.prototype.takeSubmission = function takeSubmission() {
    var submission = this.eligible.shift();
    submission.givenCount++;
    this.hashes[submission.hash] = true;
    this.received.push(submission);
}

// Class
module.exports = class Olympic {

    constructor(params) {
        this.callback = params.callback;
        this.organizations = params.organizations;
        this.submissions = params.submissions;
        this.start();
    }

    start() {
        Promise.try(f => {
            // Modifying organization and submissions objects
            _.each(this.organizations, organization => {
                organization.eligible = [];
                organization.eligibleCount = 0;
                organization.received = [];
                organization.source = organization.sources[0];
                organization.sourced = [];
                organization.states = organization.states || [];
                organization.swappableSourced = [];
            });

            _.each(this.submissions.hashes, submission => {
                // Used to determine rarity of submission
                submission.eligible = [];
                submission.givenCount = 0;
            });
        })

        // Assigning submissions to organizations
        .then(f => {
            var sourceMap = {};

            _.each(this.organizations, organization => {
                _.each(organization.sources, source => {
                    sourceMap[source] = organization;
                });
            });

            _.each(this.submissions.hashes, submission => {
                submission.sourceObjects = [];

                _.each(_.keys(submission.sources), source => {
                    var organization = sourceMap[source];
                    if (organization) {
                        organization.hashes[submission.hash] = true;
                        organization.sourced.push(submission);
                        submission.sourceObjects.push(organization);

                        if (submission.swappable) {
                            organization.swappableSourced.push(submission);
                        }
                    }
                });
            });
        })

        // Remove duplicates
        .then(f => {
            _.each(this.submissions.hashes, submission => {
                submission.sourceObjects = _.uniq(submission.sourceObjects);
            });

            _.each(this.organizations, organization => {
                organization.sourced = _.uniq(organization.sourced);
                organization.swappableSourced = _.uniq(organization.swappableSourced);
            });
        })

        // Saving sourced signatures
        .then(f => this.organizations)
        .each(organization => {
            var sourced = _.map(organization.sourced, submission => submission.row);
            return stringify(sourced, {
                header: true,
                quoted: true,
            }).then(csv => {
                var filename = path.join(__dirname, `../../output/${organization.source}-own.csv`);
                fs.writeFileSync(filename, csv);
            });
        })

        // Removing organizations that aren't swapping
        .then(f => {
            this.organizations = _.filter(this.organizations, organization => organization.swapping);
        })

        // Assigning eligible organizations to each submission
        .then(f => {
            // Assigning eligiblity to submissions
            _.each(this.submissions.swappableHashes, submission => {
                _.each(this.organizations, organization => {
                    // Skip suppressed hashes
                    if (organization.hashes[submission.hash]) {
                        return;
                    }

                    // Used to determine rarity of submission
                    submission.eligible.push(organization);
                });
            });
        })

        // Assigning eligible submissions to organizations
        .then(f => {
            _.each(this.submissions.swappableHashes, submission => {
                _.each(submission.eligible, organization => {
                    organization.eligible.push(submission);
                });
            });
        })

        // Sorting eligible submissions
        .then(f => {
            _.each(this.organizations, organization => {
                this.sortEligibleSubmissionsForOrganization(organization);
            });
        })

        // Show amount of eligible hashes per organization
        .then(f => {
            console.log(`Eligible hashes for...`);
            _.each(this.organizations, organization => {
                var eligible = {};
                var eligibleInState = {};

                _.each(organization.eligible, submission => {
                    eligible[submission.hash] = true;

                    if (organization.states.length > 0 && _.includes(organization.states, submission.row.state)) {
                        eligibleInState[submission.hash] = true;
                    }
                });

                organization.eligibleCount = _.size(eligible);
                organization.eligibleInStateCount = _.size(eligibleInState);

                var message = `- ${chalk.yellow(organization.name)}:\n`;

                if (organization.states.length) {
                    message += `    Geolocated: ${chalk.green(organization.eligibleInStateCount)}\n`;
                }

                message += `    Total: ${chalk.green(organization.eligibleCount)}`;

                console.log(message);
            });
        })

        // Swap
        .then(f => {
            var finish;
            var promise = new Promise((resolve, reject) => {
                finish = resolve;
            });

            var organizations = this.getSortedListOfOwedOrganizations();

            async.forever(next => {
                // Check if every organization has bounced
                if (_.isEmpty(organizations)) {
                    next(1);
                    return;
                }

                var organization = organizations[0];

                // // Find the organization who is the farthest from matching their sourced count
                // var organization = _.maxBy(
                //     organizations,
                //     // organization => organization.swappableSourced.length / organization.eligibleCount // Ratio
                //     organization => organization.swappableSourced.length - organization.received.length // Sum
                // );

                if (organization.received.length === organization.swappableSourced.length) {
                    // Remove organization
                    _.pull(organizations, organization);

                    // Sort next organization
                    organization = organizations[0];
                    this.sortEligibleSubmissionsForOrganization(organization);

                    // End iteration
                    next();
                    return;
                }

                // Find next submission
                var submission = organization.hasSubmissionAvailable();

                if (!submission) {
                    _.pull(organizations, organization);
                    next();
                    return;
                }

                organization.takeSubmission();

                next();
            }, err => {
                finish();
            });

            return promise;
        })

        // Save summary
        .then(f => {
            var filename = path.join(__dirname, `../../output/summary.csv`);
            var summary = 'Organization,Sourced,Eligible,Received,Percent\n';
            _.each(this.organizations, organization => {
                var eligibleCount = organization.eligibleCount;
                var name = organization.name;
                var receivedTotal = organization.received.length;
                var sourced = organization.swappableSourced.length;
                var percent = ((receivedTotal / sourced) * 100).toFixed(2);
                summary += `${name},${sourced},${eligibleCount},${receivedTotal},${percent}%\n`;
            });
            fs.writeFileSync(filename, summary);
        })

        // Save organization files
        .then(f => this.organizations)
        .each(organization => {
            var received = _.map(organization.received, submission => submission.row);
            return stringify(received, {
                header: true,
                quoted: true,
            }).then(csv => {
                var filename = path.join(__dirname, `../../output/${organization.source}-swapped.csv`);
                fs.writeFileSync(filename, csv);
            });
        })

        // Finish
        .then(f => {
            var count = _.sumBy(this.organizations, organization => {
                return organization.received.length;
            });
            console.log(`Finished. In total, ${chalk.green(count)} names were swapped.`);
        });
    }

    getSortedListOfOwedOrganizations() {
        return _.filter(this.organizations, organization => {
            return (
                organization.swappableSourced.length > organization.received.length
            );
        }).sort((a, b) => {
            // Sort organizations with a higher sourced/eligible ratio earlier
            var ratioA = a.swappableSourced.length / a.eligibleCount;
            var ratioB = b.swappableSourced.length / b.eligibleCount;
            return ratioB - ratioA;
        });
    }

    sortEligibleSubmissionsForOrganization(organization) {
        if (!organization) {
            return;
        }

        var sortingByState = (organization.states && organization.states.length > 0);

        organization.eligible.sort((submissionA, submissionB) => {
            // Sorting less-swapped names earlier
            if (submissionA.givenCount !== submissionB.givenCount) {
                return submissionA.givenCount - submissionB.givenCount;
            }

            // Sorting names in preferred states earlier
            if (sortingByState) {
                var inStateA = +_.includes(organization.states, submissionA.row.state);
                var inStateB = +_.includes(organization.states, submissionB.row.state);

                if (inStateA !== inStateB) {
                    return inStateB - inStateA;
                }
            }

            // Sorting well-known names earlier
            if (submissionA.eligible.length !== submissionB.eligible.length) {
                return submissionA.eligible.length - submissionB.eligible.length;
            }

            return 0;
        });
    }
}
<|endoftext|>


<commit_before>var Base = require('./base');
var CartoDBLayer = require('../../../geo/map/cartodb-layer');
var SourceBase = require('../source/base');
var StyleBase = require('../style/base');
var CartoError = require('../error');
/**
 * Represent a layer Object.
 *
 * @param {object} source - The source where the layer will fetch the data
 * @param {carto.style.CartoCSS} style - A CartoCSS object with the layer styling
 * @param {object} [options]
 * @param {Array<string>} [options.featureClickColumns=[]] - Columns that will be available for `featureClick` events
 * @param {Array<string>} [options.featureOverColumns=[]] - Columns that will be available for `featureOver` events
 * @fires carto.layer.Layer.FeatureEvent
 * @fires carto.layer.Layer.sourceChanged
 * @fires carto.layer.Layer.styleChanged
 * @example
 * // no options
 * new carto.layer.Layer(citiesSource, citiesStyle);
 * @example
 * // with options
 * new carto.layer.Layer(citiesSource, citiesStyle, {
 *   featureClickColumns: [ 'name', 'population' ],
 *   featureOverColumns: [ 'name' ]
 * });
 * @constructor
 * @extends carto.layer.Base
 * @memberof carto.layer
 * @api
 */
function Layer (source, style, options) {
  options = options || {};

  _checkSource(source);
  _checkStyle(style);

  this._engine = undefined;
  this._internalModel = undefined;

  this._source = source;
  this._style = style;
  this._visible = true;
  this._featureClickColumns = options.featureClickColumns || [];
  this._featureOverColumns = options.featureOverColumns || [];

  Base.apply(this, arguments);
}

Layer.prototype = Object.create(Base.prototype);

/**
 * Set a new style for this layer.
 *
 * @param {carto.style.CartoCSS} New style
 * @fires carto.layer.Layer.styleChanged
 * @return {carto.layer.Layer} this
 *
 * @api
 */
Layer.prototype.setStyle = function (style, opts) {
  var prevStyle = this._style;
  _checkStyle(style);
  opts = opts || {};
  this._style = style;
  if (this._internalModel) {
    this._internalModel.set('cartocss', style.toCartoCSS());
  }
  if (prevStyle !== style) {
    this.trigger('styleChanged', this);
  }
  return this;
};

/**
 * Get the current style for this layer.
 *
 * @return {carto.style.CartoCSS} Current style
 * @api
 */
Layer.prototype.getStyle = function () {
  return this._style;
};

/**
 * Set a new source for this layer.
 *
 * A source and a layer must belong to the same client so you can't
 * add a source belonging to a different client.
 *
 * @param {carto.source.Dataset|carto.source.SQL} source New source
 * @fires carto.layer.Layer.sourceChanged
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.setSource = function (source) {
  var prevSource = this._source;
  _checkSource(source);
  if (this._internalModel) {
    // If the source already has an engine and is different from the layer's engine throw an error.
    if (source.$getEngine() && source.$getEngine() !== this._internalModel._engine) {
      throw new Error('A layer can\'t have a source which belongs to a different client');
    }
    this._internalModel.set('source', source.$getInternalModel());
  }
  this._source = source;
  if (prevSource !== source) {
    this.trigger('sourceChanged', this);
  }
  return this;
};

/**
 * Get the current source for this layer.
 *
 * @return {carto.source.Dataset|carto.source.SQL} Current source
 * @api
 */
Layer.prototype.getSource = function () {
  return this._source;
};

/**
 * Set new columns for `featureClick` events.
 *
 * @param {Array<string>} columns An array containing column names
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.setFeatureClickColumns = function (columns) {
  this._featureClickColumns = columns;
  if (this._internalModel) {
    this._internalModel.infowindow.update(_getInteractivityFields(columns));
  }

  return this;
};

/**
 * Return the columns available in `featureClicked` events.
 *
 * @return  {Array<string>} Column names available in `featureClicked` events
 * @api
 */
Layer.prototype.getFeatureClickColumns = function (columns) {
  return this._featureClickColumns;
};

Layer.prototype.hasFeatureClickColumns = function (columns) {
  return this.getFeatureClickColumns().length > 0;
};

/**
 * Set new columns for `featureOver` events.
 *
 * @param {Array<string>} columns An array containing column names
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.setFeatureOverColumns = function (columns) {
  this._featureOverColumns = columns;
  if (this._internalModel) {
    this._internalModel.tooltip.update(_getInteractivityFields(columns));
  }

  return this;
};

/**
 * Return the columns available in `featureOver` events.
 *
 * @return  {Array<string>} Column names available in `featureOver` events
 * @api
 */
Layer.prototype.getFeatureOverColumns = function (columns) {
  return this._featureOverColumns;
};

Layer.prototype.hasFeatureOverColumns = function (columns) {
  return this.getFeatureOverColumns().length > 0;
};

/**
 * Hides the layer.
 *
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.hide = function () {
  var prevStatus = this._visible;
  this._visible = false;
  if (this._internalModel) {
    this._internalModel.set('visible', false);
  }
  if (prevStatus) {
    this.trigger('visibilityChanged', false);
  }
  return this;
};

/**
 * Shows the layer.
 *
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.show = function () {
  var prevStatus = this._visible;
  this._visible = true;
  if (this._internalModel) {
    this._internalModel.set('visible', true);
  }
  if (!prevStatus) {
    this.trigger('visibilityChanged', false);
  }
  return this;
};

/**
 * Change the layer's visibility.
 *
 * @return {carto.layer.Layer} this
 */
Layer.prototype.toggle = function () {
  return this.isVisible() ? this.hide() : this.show();
};

/**
 * Return true if the layer is visible and false when not visible.
 *
 * @return {boolean} - A boolean value indicating the layer's visibility
 * @api
 */
Layer.prototype.isVisible = function () {
  return this._visible;
};

/**
 * Return `true` if the layer is not visible and false when visible.
 *
 * @return {boolean} - A boolean value indicating the layer's visibility
 * @api
 */
Layer.prototype.isHidden = function () {
  return !this.isVisible();
};

// Private functions.

Layer.prototype._createInternalModel = function (engine) {
  var internalModel = new CartoDBLayer({
    id: this._id,
    source: this._source.$getInternalModel(),
    cartocss: this._style.toCartoCSS(),
    visible: this._visible,
    infowindow: _getInteractivityFields(this._featureClickColumns),
    tooltip: _getInteractivityFields(this._featureOverColumns)
  }, { engine: engine });

  internalModel.on('change:error', function (model, value) {
    if (_isStyleError(value)) {
      this._style.$setError(new CartoError(value));
    }
  }, this);

  return internalModel;
};

// Internal functions.

Layer.prototype.$setEngine = function (engine) {
  if (this._engine) {
    return;
  }
  this._engine = engine;
  this._source.$setEngine(engine);
  if (!this._internalModel) {
    this._internalModel = this._createInternalModel(engine);
  }
};

// Scope functions

/**
 * Transform the columns array into the format expected by the CartoDBLayer.
 */
function _getInteractivityFields (columns) {
  var fields = columns.map(function (column, index) {
    return {
      name: column,
      title: true,
      position: index
    };
  });

  return {
    fields: fields
  };
}

function _checkStyle (style) {
  if (!(style instanceof StyleBase)) {
    throw new TypeError('The given object is not a valid style. See "carto.style.Base"');
  }
}

function _checkSource (source) {
  if (!(source instanceof SourceBase)) {
    throw new TypeError('The given object is not a valid source. See "carto.source.Base"');
  }
}

/**
 * Return true when a windshaft error is because a styling error.
 */
function _isStyleError (windshaftError) {
  return windshaftError.message && windshaftError.message.indexOf('style') === 0;
}

/**
 * @typedef {Object} LatLng
 * @property {number} lat - Latitude
 * @property {number} lng - Longitude
 *
 * @api
 */

/**
 * Event triggered when the source of the layer changes.
 *
 * Contains a single argument with the Layer where the source has changed.
 *
 * @event carto.layer.Layer.sourceChanged
 * @type {carto.layer.Layer}
 * @api
 */

/**
 * Event triggered when the style of the layer changes.
 *
 * Contains a single argument with the Layer where the style has changed.
 *
 * @event carto.layer.Layer.styleChanged
 * @type {carto.layer.Layer}
 * @api
 */

module.exports = Layer;
<commit_msg>Fix bug when .setOk after error<commit_after>var Base = require('./base');
var CartoDBLayer = require('../../../geo/map/cartodb-layer');
var SourceBase = require('../source/base');
var StyleBase = require('../style/base');
var CartoError = require('../error');
/**
 * Represent a layer Object.
 *
 * @param {object} source - The source where the layer will fetch the data
 * @param {carto.style.CartoCSS} style - A CartoCSS object with the layer styling
 * @param {object} [options]
 * @param {Array<string>} [options.featureClickColumns=[]] - Columns that will be available for `featureClick` events
 * @param {Array<string>} [options.featureOverColumns=[]] - Columns that will be available for `featureOver` events
 * @fires carto.layer.Layer.FeatureEvent
 * @fires carto.layer.Layer.sourceChanged
 * @fires carto.layer.Layer.styleChanged
 * @example
 * // no options
 * new carto.layer.Layer(citiesSource, citiesStyle);
 * @example
 * // with options
 * new carto.layer.Layer(citiesSource, citiesStyle, {
 *   featureClickColumns: [ 'name', 'population' ],
 *   featureOverColumns: [ 'name' ]
 * });
 * @constructor
 * @extends carto.layer.Base
 * @memberof carto.layer
 * @api
 */
function Layer (source, style, options) {
  options = options || {};

  _checkSource(source);
  _checkStyle(style);

  this._engine = undefined;
  this._internalModel = undefined;

  this._source = source;
  this._style = style;
  this._visible = true;
  this._featureClickColumns = options.featureClickColumns || [];
  this._featureOverColumns = options.featureOverColumns || [];

  Base.apply(this, arguments);
}

Layer.prototype = Object.create(Base.prototype);

/**
 * Set a new style for this layer.
 *
 * @param {carto.style.CartoCSS} New style
 * @fires carto.layer.Layer.styleChanged
 * @return {carto.layer.Layer} this
 *
 * @api
 */
Layer.prototype.setStyle = function (style, opts) {
  var prevStyle = this._style;
  _checkStyle(style);
  opts = opts || {};
  this._style = style;
  if (this._internalModel) {
    this._internalModel.set('cartocss', style.toCartoCSS());
  }
  if (prevStyle !== style) {
    this.trigger('styleChanged', this);
  }
  return this;
};

/**
 * Get the current style for this layer.
 *
 * @return {carto.style.CartoCSS} Current style
 * @api
 */
Layer.prototype.getStyle = function () {
  return this._style;
};

/**
 * Set a new source for this layer.
 *
 * A source and a layer must belong to the same client so you can't
 * add a source belonging to a different client.
 *
 * @param {carto.source.Dataset|carto.source.SQL} source New source
 * @fires carto.layer.Layer.sourceChanged
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.setSource = function (source) {
  var prevSource = this._source;
  _checkSource(source);
  if (this._internalModel) {
    // If the source already has an engine and is different from the layer's engine throw an error.
    if (source.$getEngine() && source.$getEngine() !== this._internalModel._engine) {
      throw new Error('A layer can\'t have a source which belongs to a different client');
    }
    this._internalModel.set('source', source.$getInternalModel());
  }
  this._source = source;
  if (prevSource !== source) {
    this.trigger('sourceChanged', this);
  }
  return this;
};

/**
 * Get the current source for this layer.
 *
 * @return {carto.source.Dataset|carto.source.SQL} Current source
 * @api
 */
Layer.prototype.getSource = function () {
  return this._source;
};

/**
 * Set new columns for `featureClick` events.
 *
 * @param {Array<string>} columns An array containing column names
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.setFeatureClickColumns = function (columns) {
  this._featureClickColumns = columns;
  if (this._internalModel) {
    this._internalModel.infowindow.update(_getInteractivityFields(columns));
  }

  return this;
};

/**
 * Return the columns available in `featureClicked` events.
 *
 * @return  {Array<string>} Column names available in `featureClicked` events
 * @api
 */
Layer.prototype.getFeatureClickColumns = function (columns) {
  return this._featureClickColumns;
};

Layer.prototype.hasFeatureClickColumns = function (columns) {
  return this.getFeatureClickColumns().length > 0;
};

/**
 * Set new columns for `featureOver` events.
 *
 * @param {Array<string>} columns An array containing column names
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.setFeatureOverColumns = function (columns) {
  this._featureOverColumns = columns;
  if (this._internalModel) {
    this._internalModel.tooltip.update(_getInteractivityFields(columns));
  }

  return this;
};

/**
 * Return the columns available in `featureOver` events.
 *
 * @return  {Array<string>} Column names available in `featureOver` events
 * @api
 */
Layer.prototype.getFeatureOverColumns = function (columns) {
  return this._featureOverColumns;
};

Layer.prototype.hasFeatureOverColumns = function (columns) {
  return this.getFeatureOverColumns().length > 0;
};

/**
 * Hides the layer.
 *
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.hide = function () {
  var prevStatus = this._visible;
  this._visible = false;
  if (this._internalModel) {
    this._internalModel.set('visible', false);
  }
  if (prevStatus) {
    this.trigger('visibilityChanged', false);
  }
  return this;
};

/**
 * Shows the layer.
 *
 * @return {carto.layer.Layer} this
 * @api
 */
Layer.prototype.show = function () {
  var prevStatus = this._visible;
  this._visible = true;
  if (this._internalModel) {
    this._internalModel.set('visible', true);
  }
  if (!prevStatus) {
    this.trigger('visibilityChanged', false);
  }
  return this;
};

/**
 * Change the layer's visibility.
 *
 * @return {carto.layer.Layer} this
 */
Layer.prototype.toggle = function () {
  return this.isVisible() ? this.hide() : this.show();
};

/**
 * Return true if the layer is visible and false when not visible.
 *
 * @return {boolean} - A boolean value indicating the layer's visibility
 * @api
 */
Layer.prototype.isVisible = function () {
  return this._visible;
};

/**
 * Return `true` if the layer is not visible and false when visible.
 *
 * @return {boolean} - A boolean value indicating the layer's visibility
 * @api
 */
Layer.prototype.isHidden = function () {
  return !this.isVisible();
};

// Private functions.

Layer.prototype._createInternalModel = function (engine) {
  var internalModel = new CartoDBLayer({
    id: this._id,
    source: this._source.$getInternalModel(),
    cartocss: this._style.toCartoCSS(),
    visible: this._visible,
    infowindow: _getInteractivityFields(this._featureClickColumns),
    tooltip: _getInteractivityFields(this._featureOverColumns)
  }, { engine: engine });

  internalModel.on('change:error', function (model, value) {
    if (value && _isStyleError(value)) {
      this._style.$setError(new CartoError(value));
    }
  }, this);

  return internalModel;
};

// Internal functions.

Layer.prototype.$setEngine = function (engine) {
  if (this._engine) {
    return;
  }
  this._engine = engine;
  this._source.$setEngine(engine);
  if (!this._internalModel) {
    this._internalModel = this._createInternalModel(engine);
  }
};

// Scope functions

/**
 * Transform the columns array into the format expected by the CartoDBLayer.
 */
function _getInteractivityFields (columns) {
  var fields = columns.map(function (column, index) {
    return {
      name: column,
      title: true,
      position: index
    };
  });

  return {
    fields: fields
  };
}

function _checkStyle (style) {
  if (!(style instanceof StyleBase)) {
    throw new TypeError('The given object is not a valid style. See "carto.style.Base"');
  }
}

function _checkSource (source) {
  if (!(source instanceof SourceBase)) {
    throw new TypeError('The given object is not a valid source. See "carto.source.Base"');
  }
}

/**
 * Return true when a windshaft error is because a styling error.
 */
function _isStyleError (windshaftError) {
  return windshaftError.message && windshaftError.message.indexOf('style') === 0;
}

/**
 * @typedef {Object} LatLng
 * @property {number} lat - Latitude
 * @property {number} lng - Longitude
 *
 * @api
 */

/**
 * Event triggered when the source of the layer changes.
 *
 * Contains a single argument with the Layer where the source has changed.
 *
 * @event carto.layer.Layer.sourceChanged
 * @type {carto.layer.Layer}
 * @api
 */

/**
 * Event triggered when the style of the layer changes.
 *
 * Contains a single argument with the Layer where the style has changed.
 *
 * @event carto.layer.Layer.styleChanged
 * @type {carto.layer.Layer}
 * @api
 */

module.exports = Layer;
<|endoftext|>