<commit_before>import Ember from 'ember';
import Subscription from '../utils/subscription';
import modal from '../utils/modal';
import persistence from '../utils/persistence';
import progress_tracker from '../utils/progress_tracker';
import app_state from '../utils/app_state';
import session from '../utils/session';
import capabilities from '../utils/capabilities';
import i18n from '../utils/i18n';

export default Ember.Component.extend({
  update_classes: Subscription.obs_func.observes.apply(Subscription.obs_func, Subscription.obs_properties),
  didInsertElement: function() {
    if(this.$().width() < 850) {
      this.$().addClass('skinny_subscription');
    }
    this.set('session', session);
    session.check_token();

    if((this.get('session.invalid_token') || !capabilities.access_token) && !this.get('pricing_only')) {
      console.error('subscription_missing_access_token');
      this.set('not_authenticated', true);
    }
    this.update_classes();
  },
  update_not_authenticated: function() {
    if(!this.get('pricing_only') && this.get('session.invalid_token')) {
      this.set('not_authenticated', true);
    }
  }.observes('session.invalid_token', 'pricing_only'),
  actions: {
    toggle_explanation: function(type) {
      this.set('explanation_' + type, !this.get('explanation_' + type));
    },
    show_expiration_notes: function() {
      this.set('show_expiration_notes', true);
    },
    show_alternative_pricing: function() {
      this.set('show_alternative_pricing', !this.get('show_alternative_pricing'));
    },
    skip_subscription: function() {
      var role = this.get('subscription.user_type');
      var user = this.get('user');
      user.set('preferences.role', role);
      var progress = user.get('preferences.progress') || {};
      progress.skipped_subscribe_modal = true;
      user.set('preferences.progress', progress);
      user.save().then(null, function() { });
      // TODO: this really belongs in the modal controller
      this.sendAction('subscription_skip');
    },
    reload: function() {
      location.reload();
    },
    reset: function() {
      this.get('subscription').reset();
    },
    set_user_type: function(type) {
      this.set('subscription.user_type', type);
    },
    set_subscription_type: function(type) {
      if(type && (type.match(/communicator/) || type.match(/gift_code/))) {
        this.set('subscription.user_type', 'communicator');
        type = type.replace(/_communicator/, '');
      }
      this.set('subscription.subscription_type', type);
    },
    set_special_subscription: function() {
      this.set('subscription.special_type', !this.get('subscription.special_type'));
    },
    set_subscription: function(amount) {
      if(amount && amount.match(/slp/)) {
        this.set('subscription.user_type', 'supporter');
      }
      this.set('subscription.subscription_amount', amount);
    },
    show_options: function() {
      this.set('subscription.show_options', true);
      this.set('subscription.show_cancel', false);
    },
    purchase: function() {
      var subscription = this.get('subscription');
      var user = this.get('user');
      if(!Subscription.ready || !subscription || !user) {
        modal.error(i18n.t('purchasing_not_read', "There was a problem initializing the purchasing system. Please contact support."));
        return;
      } else if(!subscription.get('valid')) {
        return;
      }
      var _this = this;
      var subscribe = function(token, type) {
        subscription.set('finalizing_purchase', true);
        persistence.ajax('/api/v1/users/' + user.get('user_name') + '/subscription', {
          type: 'POST',
          data: {
            token: token,
            type: type,
            confirmation: subscription.get('confirmation')
          }
        }).then(function(data) {
          progress_tracker.track(data.progress, function(event) {
            if(event.status == 'errored') {
              _this.sendAction('subscription_error', i18n.t('user_subscription_update_failed', "Purchase failed. Please try again or contact support for help."));
              _this.send('reset');
              console.log(event);
              if(event.sub_status == 'server_unresponsive') {
                console.error('purchase_server_timeout');
              } else {
                console.error('purchase_progress_failed');
              }
            } else if(event.status == 'finished' && event.result && event.result.success === false && event.result.error == 'card_declined') {
              _this.sendAction('subscription_error', i18n.t('card_declined', "Purchase failed, your card was declined. Please try a different card or contact support for help."));
              _this.send('reset');
              console.log(event);
            } else if(event.status == 'finished') {
              if(user.get('preferences')) {
                user.reload().then(function() {
                  user.set('preferences.progress.subscription_set', true);
                  user.save();
                  _this.send('reset');
                  _this.sendAction('subscription_success', i18n.t('user_subscribed', "Your purchase succeeded! Thank you for supporting CoughDrop!"));
                }, function() {
                  _this.sendAction('subscription_error', i18n.t('user_subscription_reload_failed', "Purchase succeeded, but there was a problem reloading your user account. Please try loading this page again."));
                });
              } else {
                location.reload();
              }
            }
          });
        }, function(err) {
          console.log(err);
          console.error('purchase_subscription_start_failed');
          _this.send('reset');
          var message = (err.result && err.result.error) || err.error || err;
          if(message && message.match(/Access token required/)) {
            console.error('purchase_subscription_missing_token');
            _this.sendAction('subscription_authentication_error', i18n.t('user_subscription_unauthenticated', "Purchase failed, it looks like your login may have timed out. Please try logging out and back in. If that doesn't help, please contact support and we'll help figure things out."));
          } else {
            _this.sendAction('subscription_error', i18n.t('user_subscription_update_failed', "Purchase failed unexpectedly. Please try logging out and back in. If that doesn't work, please contact support for help."));
          }
        });
      };

      if(subscription.get('gift_type')) {
        subscribe({code: subscription.get('gift_code')}, 'gift_code');
      } else {
        Subscription.purchase(subscription).then(function(result) {
          console.error('purchase_promise_resolved');
          subscribe(result, subscription.get('subscription_amount_plus_trial'));
        }, function() {
          modal.error(i18n.t('purchasing_not_completed', "There was an unexpected problem completing your purchase"));
          console.error('purchase_promise_rejected');
        });
      }
    }
  }
});
<commit_msg>more card declined details<commit_after>import Ember from 'ember';
import Subscription from '../utils/subscription';
import modal from '../utils/modal';
import persistence from '../utils/persistence';
import progress_tracker from '../utils/progress_tracker';
import app_state from '../utils/app_state';
import session from '../utils/session';
import capabilities from '../utils/capabilities';
import i18n from '../utils/i18n';

export default Ember.Component.extend({
  update_classes: Subscription.obs_func.observes.apply(Subscription.obs_func, Subscription.obs_properties),
  didInsertElement: function() {
    if(this.$().width() < 850) {
      this.$().addClass('skinny_subscription');
    }
    this.set('session', session);
    session.check_token();

    if((this.get('session.invalid_token') || !capabilities.access_token) && !this.get('pricing_only')) {
      console.error('subscription_missing_access_token');
      this.set('not_authenticated', true);
    }
    this.update_classes();
  },
  update_not_authenticated: function() {
    if(!this.get('pricing_only') && this.get('session.invalid_token')) {
      this.set('not_authenticated', true);
    }
  }.observes('session.invalid_token', 'pricing_only'),
  actions: {
    toggle_explanation: function(type) {
      this.set('explanation_' + type, !this.get('explanation_' + type));
    },
    show_expiration_notes: function() {
      this.set('show_expiration_notes', true);
    },
    show_alternative_pricing: function() {
      this.set('show_alternative_pricing', !this.get('show_alternative_pricing'));
    },
    skip_subscription: function() {
      var role = this.get('subscription.user_type');
      var user = this.get('user');
      user.set('preferences.role', role);
      var progress = user.get('preferences.progress') || {};
      progress.skipped_subscribe_modal = true;
      user.set('preferences.progress', progress);
      user.save().then(null, function() { });
      // TODO: this really belongs in the modal controller
      this.sendAction('subscription_skip');
    },
    reload: function() {
      location.reload();
    },
    reset: function() {
      this.get('subscription').reset();
    },
    set_user_type: function(type) {
      this.set('subscription.user_type', type);
    },
    set_subscription_type: function(type) {
      if(type && (type.match(/communicator/) || type.match(/gift_code/))) {
        this.set('subscription.user_type', 'communicator');
        type = type.replace(/_communicator/, '');
      }
      this.set('subscription.subscription_type', type);
    },
    set_special_subscription: function() {
      this.set('subscription.special_type', !this.get('subscription.special_type'));
    },
    set_subscription: function(amount) {
      if(amount && amount.match(/slp/)) {
        this.set('subscription.user_type', 'supporter');
      }
      this.set('subscription.subscription_amount', amount);
    },
    show_options: function() {
      this.set('subscription.show_options', true);
      this.set('subscription.show_cancel', false);
    },
    purchase: function() {
      var subscription = this.get('subscription');
      var user = this.get('user');
      if(!Subscription.ready || !subscription || !user) {
        modal.error(i18n.t('purchasing_not_read', "There was a problem initializing the purchasing system. Please contact support."));
        return;
      } else if(!subscription.get('valid')) {
        return;
      }
      var _this = this;
      var subscribe = function(token, type) {
        subscription.set('finalizing_purchase', true);
        persistence.ajax('/api/v1/users/' + user.get('user_name') + '/subscription', {
          type: 'POST',
          data: {
            token: token,
            type: type,
            confirmation: subscription.get('confirmation')
          }
        }).then(function(data) {
          progress_tracker.track(data.progress, function(event) {
            if(event.status == 'errored') {
              _this.sendAction('subscription_error', i18n.t('user_subscription_update_failed', "Purchase failed. Please try again or contact support for help."));
              _this.send('reset');
              console.log(event);
              if(event.sub_status == 'server_unresponsive') {
                console.error('purchase_server_timeout');
              } else {
                console.error('purchase_progress_failed');
              }
            } else if(event.status == 'finished' && event.result && event.result.success === false && event.result.error == 'card_declined') {
              var str = i18n.t('card_declined', "Purchase failed, your card was declined. Please try a different card or contact support for help.");
              if(event.result.decline_code && event.result.decline_code == 'fraudulent') {
                str = i18n.t('card_declined_by_billing', "Purchase failed, our billing system has flagged your card as high-risk. Please try a different card or contact support for help.");
              }
              _this.sendAction('subscription_error', str);
              _this.send('reset');
              console.log(event);
            } else if(event.status == 'finished') {
              if(user.get('preferences')) {
                user.reload().then(function() {
                  user.set('preferences.progress.subscription_set', true);
                  user.save();
                  _this.send('reset');
                  _this.sendAction('subscription_success', i18n.t('user_subscribed', "Your purchase succeeded! Thank you for supporting CoughDrop!"));
                }, function() {
                  _this.sendAction('subscription_error', i18n.t('user_subscription_reload_failed', "Purchase succeeded, but there was a problem reloading your user account. Please try loading this page again."));
                });
              } else {
                location.reload();
              }
            }
          });
        }, function(err) {
          console.log(err);
          console.error('purchase_subscription_start_failed');
          _this.send('reset');
          var message = (err.result && err.result.error) || err.error || err;
          if(message && message.match(/Access token required/)) {
            console.error('purchase_subscription_missing_token');
            _this.sendAction('subscription_authentication_error', i18n.t('user_subscription_unauthenticated', "Purchase failed, it looks like your login may have timed out. Please try logging out and back in. If that doesn't help, please contact support and we'll help figure things out."));
          } else {
            _this.sendAction('subscription_error', i18n.t('user_subscription_update_failed', "Purchase failed unexpectedly. Please try logging out and back in. If that doesn't work, please contact support for help."));
          }
        });
      };

      if(subscription.get('gift_type')) {
        subscribe({code: subscription.get('gift_code')}, 'gift_code');
      } else {
        Subscription.purchase(subscription).then(function(result) {
          console.error('purchase_promise_resolved');
          subscribe(result, subscription.get('subscription_amount_plus_trial'));
        }, function() {
          modal.error(i18n.t('purchasing_not_completed', "There was an unexpected problem completing your purchase"));
          console.error('purchase_promise_rejected');
        });
      }
    }
  }
});
<|endoftext|>


<commit_before>import { connect } from 'react-redux';

import Component from './component';
import * as actions from './actions';

const splitFirstName = fullName => {
  const arr = fullName.split(' ');
  return arr.slice(0, arr.length / 2).join(' ');
};

const splitLastName = fullName => {
  const arr = fullName.split(' ');
  return arr.slice(arr.length / 2, arr.length).join(' ');
};

const mapStateToProps = ({ myGfw, countryData }) => ({
  countries: countryData && countryData.countries,
  ...(myGfw &&
    myGfw.data && {
    initialValues: {
      ...myGfw.data,
      firstName:
          myGfw.data.firstName ||
          (myGfw.data.fullName && splitFirstName(myGfw.data.fullName)),
      lastName:
          myGfw.data.lastName ||
          (myGfw.data.fullName && splitLastName(myGfw.data.fullName)),
      subsector:
          myGfw.data.subsector && myGfw.data.subsector.includes('Other')
            ? // From `Other: ${subsector_otherInput}` -> 'Other:' (value)
            'Other:'
            : myGfw.data.subsector,
      subsector_otherInput:
          myGfw.data.subsector && myGfw.data.subsector.includes('Other')
            ? myGfw.data.subsector.split('Other:')[1].trim()
            : null,
      howDoYouUse:
          myGfw.data.howDoYouUse &&
          myGfw.data.howDoYouUse.some(el => el.includes('Other'))
            ? [
              ...myGfw.data.howDoYouUse.filter(use => !use.includes('Other')),
              'Other'
            ]
            : myGfw.data.howDoYouUse,
      howDoYouUse_otherInput:
          myGfw.data.howDoYouUse &&
          myGfw.data.howDoYouUse.some(el => el.includes('Other'))
            ? myGfw.data.howDoYouUse
              .find(el => el.includes('Other'))
              .split('Other:')[1]
              .trim()
            : null
    }
  })
});

export default connect(mapStateToProps, actions)(Component);
<commit_msg>hotfix for my profile form not checking for valid other statement in intial values<commit_after>import { connect } from 'react-redux';

import Component from './component';
import * as actions from './actions';

const splitFirstName = fullName => {
  const arr = fullName.split(' ');
  return arr.slice(0, arr.length / 2).join(' ');
};

const splitLastName = fullName => {
  const arr = fullName.split(' ');
  return arr.slice(arr.length / 2, arr.length).join(' ');
};

const mapStateToProps = ({ myGfw, countryData }) => {
  const { howDoYouUse, subsector } = myGfw.data || {};

  const subsectorHasOther = subsector && subsector.includes('Other');
  const subsectorSplit = subsectorHasOther && subsector.split('Other:');
  const subsectorOther = subsectorSplit && subsectorSplit.length >= 2 ? subsectorSplit[1].trim() : null;

  const howDoYouUseHasOther = howDoYouUse && howDoYouUse.some(el => el.includes('Other'));
  const howDoYouUseOtherEntry = howDoYouUseHasOther && howDoYouUse.find(el => el.includes('Other'));
  const howDoYouUseSplit = howDoYouUseOtherEntry && howDoYouUseOtherEntry.split('Other:');
  const howDoYouUseOther = howDoYouUseSplit && howDoYouUseSplit.length >= 2 ? howDoYouUseSplit[1].trim() : null;

  return {
    countries: countryData && countryData.countries,
    ...(myGfw &&
      myGfw.data && {
      initialValues: {
        ...myGfw.data,
        firstName:
            myGfw.data.firstName ||
            (myGfw.data.fullName && splitFirstName(myGfw.data.fullName)),
        lastName:
            myGfw.data.lastName ||
            (myGfw.data.fullName && splitLastName(myGfw.data.fullName)),
        subsector:
            myGfw.data.subsector && myGfw.data.subsector.includes('Other')
              ? // From `Other: ${subsector_otherInput}` -> 'Other:' (value)
              'Other:'
              : myGfw.data.subsector,
        subsector_otherInput: subsectorOther,
        howDoYouUse:
            myGfw.data.howDoYouUse &&
            myGfw.data.howDoYouUse.some(el => el.includes('Other'))
              ? [
                ...myGfw.data.howDoYouUse.filter(use => !use.includes('Other')),
                'Other'
              ]
              : myGfw.data.howDoYouUse,
        howDoYouUse_otherInput: howDoYouUseOther
      }
    })
  };
};

export default connect(mapStateToProps, actions)(Component);
<|endoftext|>


<commit_before>'use strict';

(function() {
  angular.module('ncsaas')
    .service('baseResourceListController',
    ['baseControllerListClass',
    'ENV',
    'resourcesService',
    'priceEstimationService',
    'servicesService',
    'currentStateService',
    'projectsService',
    '$uibModal',
    '$rootScope',
    '$state',
    '$q',
    'resourceUtils',
    'ncUtils',
    baseResourceListController
    ]);

  // need for resource tab
  function baseResourceListController(
    baseControllerListClass,
    ENV,
    resourcesService,
    priceEstimationService,
    servicesService,
    currentStateService,
    projectsService,
    $uibModal,
    $rootScope,
    $state,
    $q,
    resourceUtils,
    ncUtils) {
    var ControllerListClass = baseControllerListClass.extend({
      init: function() {
        this.service = resourcesService;
        this.categories = {};
        this.categories[ENV.VirtualMachines] = 'vms';
        this.categories[ENV.Applications] = 'apps';
        this.categories[ENV.PrivateClouds] = 'private_clouds';
        this.categories[ENV.Storages] = 'storages';
        this.enableRefresh = true;

        this._super();
        this.hasCustomFilters = false;
      },
      getTableOptions: function() {
        var vm = this.controllerScope;
        return {
          searchFieldName: 'name',
          noDataText: 'You have no resources yet.',
          noMatchesText: 'No resources found matching filter.',
          columns: [
            {
              title: 'Name',
              className: 'all',
              render: function(row) {
                var img = ''
                      .replace('{src}', resourceUtils.getIcon(row))
                      .replace('{title}', row.resource_type);
                var href = $state.href('resources.details', {
                  uuid: row.uuid,
                  resource_type: row.resource_type
                });
                return ncUtils.renderLink(href, img + ' ' + row.name);
              }
            },
            {
              title: 'Provider',
              className: 'desktop',
              render: function(row) {
                return row.service_name;
              }
            },
            {
              title: 'State',
              className: 'min-tablet-l',
              render: function(row) {
                var uuids = vm.list.map(function(item) {
                  return item.uuid;
                });
                var index = uuids.indexOf(row.uuid);
                return '<resource-state resource="controller.list[{index}]"></resource-state>'
                  .replace('{index}', index);
              }
            }
          ],
          tableActions: this.getTableActions(),
          rowActions: function(row) {
            var index;
            for (var i = 0; i < this.list.length; i++) {
              if (this.list[i].uuid === row.uuid) {
                index = i;
              }
            }
            return '<action-button-resource button-controller="controller" button-model="controller.list[' + index + ']"/>';
          }
        };
      },
      getTableActions: function() {
        var actions = [];
        if (ENV.featuresVisible || ENV.toBeFeatures.indexOf('import') === -1) {
          actions.push(this.getImportAction());
        }
        if (this.category !== undefined) {
          actions.push(this.getCreateAction());
        }
        if (ENV.featuresVisible || ENV.toBeFeatures.indexOf('openMap') === -1) {
          actions.push(this.getMapAction());
        }
        return actions;
      },
      getImportAction: function() {
        var disabled, tooltip;
        if (ncUtils.isCustomerQuotaReached(this.currentCustomer, 'resource')) {
          disabled = true;
          tooltip = 'Quota has been reached';
        } else if (!this.projectHasNonSharedService(this.currentProject)) {
          disabled = true;
          tooltip = 'Import is not possible as there are no personal provider accounts registered.';
        } else {
          disabled = false;
          tooltip = 'Import resources from the registered provider accounts.';
        }
        return {
          name: ' ' + this.getImportTitle(),
          callback: function() {
            $state.go('import.import');
          },
          disabled: disabled,
          titleAttr: tooltip
        };
      },
      getImportTitle: function() {
        return 'Import';
      },
      getCreateAction: function() {
        var disabled, tooltip;
        if (ncUtils.isCustomerQuotaReached(this.currentCustomer, 'resource')) {
          disabled = true;
          tooltip = 'Quota has been reached';
        }
        return {
          name: ' ' + this.getCreateTitle(),
          callback: function() {
            this.gotoAppstore();
          }.bind(this),
          disabled: disabled,
          titleAttr: tooltip
        };
      },
      getCreateTitle: function() {
        return 'Create';
      },
      gotoAppstore: function() {
        $state.go(this.getCategoryState(), {
          uuid: this.currentProject.uuid
        });
      },
      getCategoryState() {
        if (this.category === ENV.VirtualMachines) {
          return 'appstore.vms';
        } else if (this.category === ENV.PrivateClouds) {
          return 'appstore.private_clouds';
        } else if (this.category === ENV.Applications) {
          return 'appstore.apps';
        } else if (this.category === ENV.Storages) {
          return 'appstore.storages';
        }
      },
      getMapAction: function() {
        return {
          name: ' Open map',
          callback: this.openMap.bind(this)
        };
      },
      rowFields: [
        'uuid', 'url', 'name', 'state', 'runtime_state', 'created', 'error_message',
        'resource_type', 'latitude', 'longitude',
        'service_name', 'service_uuid', 'customer', 'service_settings_state',
        'service_settings_error_message', 'service_settings_uuid', 'security_groups'
      ],
      getMarkers: function() {
        var items = this.controllerScope.list.filter(function hasCoordinates(item) {
          return item.latitude != null && item.longitude != null;
        });

        var points = {};
        items.forEach(function groupMarkersByCoordinates(item) {
          var key = [item.latitude, item.longitude];
          if (!points[key]) {
            points[key] = [];
          }
          points[key].push(item);
        });

        var markers = [];
        angular.forEach(points, function createMarker(items) {
          var item = items[0];
          var message = items.map(function(item) {
            return item.name;
          }).join('
');
          markers.push({
            lat: item.latitude,
            lng: item.longitude,
            message: message
          });
        });
        return markers;
      },
      openMap: function() {
        var markers = this.getMarkers();

        if(!markers) {
          alert('No virtual machines with coordinates');
        } else {
          var scope = $rootScope.$new();
          scope.markers = markers;
          scope.maxbounds = new L.LatLngBounds(markers);
          $uibModal.open({
            template: '<leaflet width="100%" markers="markers" maxbounds="maxbounds"></leaflet>',
            windowClass: 'map-dialog',
            scope: scope
          });
        }
      },
      projectHasNonSharedService: function(project) {
        for (var i = 0; i < project.services.length; i++) {
          var service = project.services[i];
          if (!service.shared) {
            return true;
          }
        }
        return false;
      },
      getList: function(filter) {
        var vm = this;
        var fn = this._super.bind(this);
        return $q.all([
          currentStateService.getCustomer().then(function(customer) {
            vm.currentCustomer = customer;
          }),
          currentStateService.getProject().then(function(project) {
            vm.currentProject = project;
          })
        ]).then(function() {
          vm.tableOptions = vm.getTableOptions();
          var query = angular.extend({}, filter, {
            field: vm.rowFields
          });
          if (angular.isDefined(vm.category) && !vm.getFilter().resource_type) {
            query.resource_category = vm.categories[vm.category];
            vm.updateFilters(filter);
          }
          return fn(query);
        });
      },
      updateFilters: function(filter) {
        var query = angular.extend({
          resource_category: this.categories[this.category],
          customer: currentStateService.getCustomerUuid()
        }, filter);
        angular.forEach(this.service.defaultFilter, function(val, key) {
          if (key != 'resource_type') {
            query[key] = val;
          }
        });
        return resourcesService.countByType(query).then(function(counts) {
          this.searchFilters = this.getFilterByCounts(counts);
        }.bind(this));
      },
      getFilterByCounts: function(counts) {
        var filters = [];
        angular.forEach(counts, function(count, resourceType) {
          if (count > 0) {
            filters.push({
              name: 'resource_type',
              title: resourceType.replace(".", " ") + ' (' + count + ')',
              value: resourceType
            });
          }
        });
        return filters;
      },
      afterGetList: function() {
        angular.forEach(this.list, function(resource) {
          resourcesService.cleanOptionsCache(resource.url);
        });
        this._super();
      },
      afterInstanceRemove: function(resource) {
        this._super(resource);
        servicesService.clearAllCacheForCurrentEndpoint();
        projectsService.clearAllCacheForCurrentEndpoint();
        priceEstimationService.clearAllCacheForCurrentEndpoint();
      },
      reInitResource:function(resource) {
        var vm = this;
        return vm.service.$get(resource.resource_type, resource.uuid).then(function(response) {
          var index = vm.list.indexOf(resource);
          vm.list[index] = response;
          vm.afterGetList();
        });
      }
    });

    return ControllerListClass;
  }
})();
<commit_msg>Fix typo.<commit_after>'use strict';

(function() {
  angular.module('ncsaas')
    .service('baseResourceListController',
    ['baseControllerListClass',
    'ENV',
    'resourcesService',
    'priceEstimationService',
    'servicesService',
    'currentStateService',
    'projectsService',
    '$uibModal',
    '$rootScope',
    '$state',
    '$q',
    'resourceUtils',
    'ncUtils',
    baseResourceListController
    ]);

  // need for resource tab
  function baseResourceListController(
    baseControllerListClass,
    ENV,
    resourcesService,
    priceEstimationService,
    servicesService,
    currentStateService,
    projectsService,
    $uibModal,
    $rootScope,
    $state,
    $q,
    resourceUtils,
    ncUtils) {
    var ControllerListClass = baseControllerListClass.extend({
      init: function() {
        this.service = resourcesService;
        this.categories = {};
        this.categories[ENV.VirtualMachines] = 'vms';
        this.categories[ENV.Applications] = 'apps';
        this.categories[ENV.PrivateClouds] = 'private_clouds';
        this.categories[ENV.Storages] = 'storages';
        this.enableRefresh = true;

        this._super();
        this.hasCustomFilters = false;
      },
      getTableOptions: function() {
        var vm = this.controllerScope;
        return {
          searchFieldName: 'name',
          noDataText: 'You have no resources yet.',
          noMatchesText: 'No resources found matching filter.',
          columns: [
            {
              title: 'Name',
              className: 'all',
              render: function(row) {
                var img = ''
                      .replace('{src}', resourceUtils.getIcon(row))
                      .replace('{title}', row.resource_type);
                var href = $state.href('resources.details', {
                  uuid: row.uuid,
                  resource_type: row.resource_type
                });
                return ncUtils.renderLink(href, img + ' ' + row.name);
              }
            },
            {
              title: 'Provider',
              className: 'desktop',
              render: function(row) {
                return row.service_name;
              }
            },
            {
              title: 'State',
              className: 'min-tablet-l',
              render: function(row) {
                var uuids = vm.list.map(function(item) {
                  return item.uuid;
                });
                var index = uuids.indexOf(row.uuid);
                return '<resource-state resource="controller.list[{index}]"></resource-state>'
                  .replace('{index}', index);
              }
            }
          ],
          tableActions: this.getTableActions(),
          rowActions: function(row) {
            var index;
            for (var i = 0; i < this.list.length; i++) {
              if (this.list[i].uuid === row.uuid) {
                index = i;
              }
            }
            return '<action-button-resource button-controller="controller" button-model="controller.list[' + index + ']"/>';
          }
        };
      },
      getTableActions: function() {
        var actions = [];
        if (ENV.featuresVisible || ENV.toBeFeatures.indexOf('import') === -1) {
          actions.push(this.getImportAction());
        }
        if (this.category !== undefined) {
          actions.push(this.getCreateAction());
        }
        if (ENV.featuresVisible || ENV.toBeFeatures.indexOf('openMap') === -1) {
          actions.push(this.getMapAction());
        }
        return actions;
      },
      getImportAction: function() {
        var disabled, tooltip;
        if (ncUtils.isCustomerQuotaReached(this.currentCustomer, 'resource')) {
          disabled = true;
          tooltip = 'Quota has been reached';
        } else if (!this.projectHasNonSharedService(this.currentProject)) {
          disabled = true;
          tooltip = 'Import is not possible as there are no personal provider accounts registered.';
        } else {
          disabled = false;
          tooltip = 'Import resources from the registered provider accounts.';
        }
        return {
          name: ' ' + this.getImportTitle(),
          callback: function() {
            $state.go('import.import');
          },
          disabled: disabled,
          titleAttr: tooltip
        };
      },
      getImportTitle: function() {
        return 'Import';
      },
      getCreateAction: function() {
        var disabled, tooltip;
        if (ncUtils.isCustomerQuotaReached(this.currentCustomer, 'resource')) {
          disabled = true;
          tooltip = 'Quota has been reached';
        }
        return {
          name: ' ' + this.getCreateTitle(),
          callback: function() {
            this.gotoAppstore();
          }.bind(this),
          disabled: disabled,
          titleAttr: tooltip
        };
      },
      getCreateTitle: function() {
        return 'Create';
      },
      gotoAppstore: function() {
        $state.go(this.getCategoryState(), {
          uuid: this.currentProject.uuid
        });
      },
      getCategoryState: function() {
        if (this.category === ENV.VirtualMachines) {
          return 'appstore.vms';
        } else if (this.category === ENV.PrivateClouds) {
          return 'appstore.private_clouds';
        } else if (this.category === ENV.Applications) {
          return 'appstore.apps';
        } else if (this.category === ENV.Storages) {
          return 'appstore.storages';
        }
      },
      getMapAction: function() {
        return {
          name: ' Open map',
          callback: this.openMap.bind(this)
        };
      },
      rowFields: [
        'uuid', 'url', 'name', 'state', 'runtime_state', 'created', 'error_message',
        'resource_type', 'latitude', 'longitude',
        'service_name', 'service_uuid', 'customer', 'service_settings_state',
        'service_settings_error_message', 'service_settings_uuid', 'security_groups'
      ],
      getMarkers: function() {
        var items = this.controllerScope.list.filter(function hasCoordinates(item) {
          return item.latitude != null && item.longitude != null;
        });

        var points = {};
        items.forEach(function groupMarkersByCoordinates(item) {
          var key = [item.latitude, item.longitude];
          if (!points[key]) {
            points[key] = [];
          }
          points[key].push(item);
        });

        var markers = [];
        angular.forEach(points, function createMarker(items) {
          var item = items[0];
          var message = items.map(function(item) {
            return item.name;
          }).join('
');
          markers.push({
            lat: item.latitude,
            lng: item.longitude,
            message: message
          });
        });
        return markers;
      },
      openMap: function() {
        var markers = this.getMarkers();

        if(!markers) {
          alert('No virtual machines with coordinates');
        } else {
          var scope = $rootScope.$new();
          scope.markers = markers;
          scope.maxbounds = new L.LatLngBounds(markers);
          $uibModal.open({
            template: '<leaflet width="100%" markers="markers" maxbounds="maxbounds"></leaflet>',
            windowClass: 'map-dialog',
            scope: scope
          });
        }
      },
      projectHasNonSharedService: function(project) {
        for (var i = 0; i < project.services.length; i++) {
          var service = project.services[i];
          if (!service.shared) {
            return true;
          }
        }
        return false;
      },
      getList: function(filter) {
        var vm = this;
        var fn = this._super.bind(this);
        return $q.all([
          currentStateService.getCustomer().then(function(customer) {
            vm.currentCustomer = customer;
          }),
          currentStateService.getProject().then(function(project) {
            vm.currentProject = project;
          })
        ]).then(function() {
          vm.tableOptions = vm.getTableOptions();
          var query = angular.extend({}, filter, {
            field: vm.rowFields
          });
          if (angular.isDefined(vm.category) && !vm.getFilter().resource_type) {
            query.resource_category = vm.categories[vm.category];
            vm.updateFilters(filter);
          }
          return fn(query);
        });
      },
      updateFilters: function(filter) {
        var query = angular.extend({
          resource_category: this.categories[this.category],
          customer: currentStateService.getCustomerUuid()
        }, filter);
        angular.forEach(this.service.defaultFilter, function(val, key) {
          if (key != 'resource_type') {
            query[key] = val;
          }
        });
        return resourcesService.countByType(query).then(function(counts) {
          this.searchFilters = this.getFilterByCounts(counts);
        }.bind(this));
      },
      getFilterByCounts: function(counts) {
        var filters = [];
        angular.forEach(counts, function(count, resourceType) {
          if (count > 0) {
            filters.push({
              name: 'resource_type',
              title: resourceType.replace(".", " ") + ' (' + count + ')',
              value: resourceType
            });
          }
        });
        return filters;
      },
      afterGetList: function() {
        angular.forEach(this.list, function(resource) {
          resourcesService.cleanOptionsCache(resource.url);
        });
        this._super();
      },
      afterInstanceRemove: function(resource) {
        this._super(resource);
        servicesService.clearAllCacheForCurrentEndpoint();
        projectsService.clearAllCacheForCurrentEndpoint();
        priceEstimationService.clearAllCacheForCurrentEndpoint();
      },
      reInitResource:function(resource) {
        var vm = this;
        return vm.service.$get(resource.resource_type, resource.uuid).then(function(response) {
          var index = vm.list.indexOf(resource);
          vm.list[index] = response;
          vm.afterGetList();
        });
      }
    });

    return ControllerListClass;
  }
})();
<|endoftext|>


<commit_before>(function() {
  'use strict';
  angular.module('dimApp')
    .directive('dimLoadoutPopup', LoadoutPopup);

  LoadoutPopup.$inject = [];

  function LoadoutPopup() {
    return {
      controller: LoadoutPopupCtrl,
      controllerAs: 'vm',
      bindToController: true,
      restrict: 'A',
      scope: {
        store: '=dimLoadoutPopup'
      },
      replace: true,
      template: [
        '
',
        '
',
        '
',
        '      + Create Loadout',
        '      From Equipped',
        '
',
        '
',
        '      {{ loadout.name }}',
        '      ',
        '      ',
        '
',
        '
',
        '       Maximize Light',
        '
',
        '
',
        '       Item Leveling',
        '
',
        '
',
        '       Gather Engrams',
        '
',
        '
',
        '       Engrams to Vault',
        '
',
        '
',
        '       {{vm.previousLoadout.name}}',
        '      All items',
        '
',
        '
',
        '
'
      ].join('')
    };
  }

  LoadoutPopupCtrl.$inject = ['$rootScope', 'ngDialog', 'dimLoadoutService', 'dimItemService', 'dimItemTier', 'toaster', 'dimEngramFarmingService'];

  function LoadoutPopupCtrl($rootScope, ngDialog, dimLoadoutService, dimItemService, dimItemTier, toaster, dimEngramFarmingService) {
    var vm = this;
    vm.previousLoadout = dimLoadoutService.previousLoadouts[vm.store.id];

    vm.classTypeId = {
      'warlock': 0,
      'titan': 1,
      'hunter': 2
    }[vm.store.class] || -1;

    function initLoadouts() {
      dimLoadoutService.getLoadouts()
        .then(function(loadouts) {
          vm.loadouts = _.sortBy(loadouts, 'name') || [];

          vm.loadouts = _.filter(vm.loadouts, function(item) {
            return vm.classTypeId === -1 || ((item.classType === -1) || (item.classType === vm.classTypeId));
          });
        });
    }
    $rootScope.$on('dim-save-loadout', initLoadouts);
    $rootScope.$on('dim-delete-loadout', initLoadouts);
    initLoadouts();

    vm.newLoadout = function newLoadout($event) {
      ngDialog.closeAll();
      $rootScope.$broadcast('dim-create-new-loadout', { });
    };

    vm.newLoadoutFromEquipped = function newLoadout($event) {
      ngDialog.closeAll();

      var loadout = loadoutFromCurrentlyEquipped(vm.store.items, "");
      // We don't want to prepopulate the loadout with a bunch of cosmetic junk
      // like emblems and ships and horns.
      loadout.items = _.pick(loadout.items,
                             'class',
                             'primary',
                             'special',
                             'heavy',
                             'helmet',
                             'gauntlets',
                             'chest',
                             'leg',
                             'classitem',
                             'artifact',
                             'ghost');
      loadout.classType = vm.classTypeId;
      vm.editLoadout(loadout, $event);
    };

    vm.deleteLoadout = function deleteLoadout(loadout, $event) {
      if (confirm("Are you sure you want to delete '" + loadout.name + "'?")) {
        dimLoadoutService.deleteLoadout(loadout);
      }
    };

    vm.editLoadout = function editLoadout(loadout, $event) {
      ngDialog.closeAll();
      $rootScope.$broadcast('dim-edit-loadout', {
        loadout: loadout
      });
    };

    function loadoutFromCurrentlyEquipped(items, name) {
      return {
        classType: -1,
        name: name,
        items: _(items)
          .chain()
          .select(function(item) {
            return item.canBeInLoadout();
          })
          .map(function (i) {
            return angular.copy(i);
          })
          .groupBy(function(i) {
            return i.type.toLowerCase();
          }).value()
      };
    }

    vm.applyLoadout = function applyLoadout(loadout, $event, filterToEquipped) {
      ngDialog.closeAll();
      dimEngramFarmingService.stop();

      if (!vm.store.isVault) {
        if (loadout === vm.previousLoadout) {
          vm.previousLoadout = undefined;
        } else {
          vm.previousLoadout = loadoutFromCurrentlyEquipped(vm.store.items, 'Before "' + loadout.name + '"');
        }
        dimLoadoutService.previousLoadouts[vm.store.id] = vm.previousLoadout; // ugly hack
      }

      if (filterToEquipped) {
        loadout = _.mapObject(loadout, function(items) {
          return _.select(items, 'equipped');
        });
      }

      dimLoadoutService.applyLoadout(vm.store, loadout);
    };

    // A dynamic loadout set up to level weapons and armor
    vm.itemLevelingLoadout = function itemLevelingLoadout($event) {
      var applicableItems = _.select(dimItemService.getItems(), function(i) {
        return i.canBeEquippedBy(vm.store) &&
          i.talentGrid && !i.talentGrid.xpComplete; // Still need XP
      });

      var bestItemFn = function(item) {
        // Leave equipped items alone if they need XP
        if (item.equipped) {
          return 1000;
        }

        var value = 0;
        // Prefer locked items (they're stuff you want to use/keep)
        // and apply different rules to them.
        if (item.locked) {
          value += 500;
          value += [
            'Common',
            'Uncommon',
            'Rare',
            'Legendary',
            'Exotic'
          ].indexOf(item.tier) * 10;
        } else {
          // For unlocked, prefer blue items so when you destroy them you get more mats.
          value += [
            'Common',
            'Uncommon',
            'Exotic',
            'Legendary',
            'Rare'
          ].indexOf(item.tier) * 10;
        }

        // Choose the item w/ the highest XP
        value += 10 * (item.talentGrid.totalXP / item.talentGrid.totalXPRequired);

        if (item.owner == vm.store.id) {
          // Prefer items owned by this character
          value += 0.5;
        } else if (item.owner == 'vault') {
          // Prefer items in the vault over items owned by a different character
          // (but not as much as items owned by this character)
          value += 0.05;
        }

        value += item.primStat ? item.primStat.value / 1000 : 0;

        return value;
      };

      var loadout = optimalLoadout(applicableItems, bestItemFn, 'Item Leveling');
      vm.applyLoadout(loadout, $event);
    };

    // Apply a loadout that's dynamically calculated to maximize Light level (preferring not to change currently-equipped items)
    vm.maxLightLoadout = function maxLightLoadout($event) {
      // These types contribute to light level
      var lightTypes = ['Primary',
                        'Special',
                        'Heavy',
                        'Helmet',
                        'Gauntlets',
                        'Chest',
                        'Leg',
                        'ClassItem',
                        'Artifact',
                        'Ghost'];

      var applicableItems = _.select(dimItemService.getItems(), function(i) {
        return i.canBeEquippedBy(vm.store) &&
          i.primStat !== undefined && // has a primary stat (sanity check)
          _.contains(lightTypes, i.type); // one of our selected types
      });

      var bestItemFn = function(item) {
        var value = item.primStat.value;

        // Break ties when items have the same stats. Note that this should only
        // add less than 0.25 total, since in the exotics special case there can be
        // three items in consideration and you don't want to go over 1 total.
        if (item.owner == vm.store.id) {
          // Prefer items owned by this character
          value += 0.1;
          if (item.equipped) {
            // Prefer them even more if they're already equipped
            value += 0.1;
          }
        } else if (item.owner == 'vault') {
          // Prefer items in the vault over items owned by a different character
          // (but not as much as items owned by this character)
          value += 0.05;
        }
        return value;
      };

      var loadout = optimalLoadout(applicableItems, bestItemFn, 'Maximize Light');
      vm.applyLoadout(loadout, $event);
    };

    // A dynamic loadout set up to level weapons and armor
    vm.gatherEngramsLoadout = function gatherEngramsLoadout($event) {
      var engrams = _.select(dimItemService.getItems(), function(i) {
        return i.isEngram() && i.sort !== 'Postmaster';
      });

      if (engrams.length === 0) {
        toaster.pop('warning', 'Gather Engrams', 'No engrams are available to transfer.');
        return;
      }

      var itemsByType = _.mapObject(_.groupBy(engrams, 'type'), function(items, type) {
        // No more than 9 engrams of a type
        return _.first(items, 9);
      });

      // Copy the items and mark them equipped and put them in arrays, so they look like a loadout
      var finalItems = {};
      _.each(itemsByType, function(items, type) {
        if (items) {
          finalItems[type.toLowerCase()] = items.map(function(i) {
            return angular.copy(i);
          });
        }
      });

      var loadout = {
        classType: -1,
        name: 'Gather Engrams',
        items: finalItems
      };
      vm.applyLoadout(loadout, $event);
    };

    vm.startFarmingEngrams = function startFarmingEngrams($event) {
      ngDialog.closeAll();
      dimEngramFarmingService.start(vm.store);
    };

    // Generate an optimized loadout based on a filtered set of items and a value function
    function optimalLoadout(applicableItems, bestItemFn, name) {
      var itemsByType = _.groupBy(applicableItems, 'type');

      var isExotic = function(item) {
        return item.tier === dimItemTier.exotic;
      };

      // Pick the best item
      var items = _.mapObject(itemsByType, function(items, type) {
        return _.max(items, bestItemFn);
      });

      // Solve for the case where our optimizer decided to equip two exotics
      var exoticGroups = [ ['Primary', 'Special', 'Heavy'], ['Helmet', 'Gauntlets', 'Chest', 'Leg'] ];
      _.each(exoticGroups, function(group) {
        var itemsInGroup = _.pick(items, group);
        var numExotics = _.select(_.values(itemsInGroup), isExotic).length;
        if (numExotics > 1) {
          var options = [];

          // Generate an option where we use each exotic
          _.each(itemsInGroup, function(item, type) {
            if (isExotic(item)) {
              var option = angular.copy(itemsInGroup);
              var optionValid = true;
              // Switch the other exotic items to the next best non-exotic
              _.each(_.omit(itemsInGroup, type), function(otherItem, otherType) {
                if (isExotic(otherItem)) {
                  var nonExotics = _.reject(itemsByType[otherType], isExotic);
                  if (_.isEmpty(nonExotics)) {
                    // this option isn't usable because we couldn't swap this exotic for any non-exotic
                    optionValid = false;
                  } else {
                    option[otherType] = _.max(nonExotics, bestItemFn);
                  }
                }
              });

              if (optionValid) {
                options.push(option);
              }
            }
          });

          // Pick the option where the optimizer function adds up to the biggest number, again favoring equipped stuff
          var bestOption = _.max(options, function(opt) { return sum(_.values(opt), bestItemFn); });
          _.assign(items, bestOption);
        }
      });

      // Copy the items and mark them equipped and put them in arrays, so they look like a loadout
      var finalItems = {};
      _.each(items, function(item, type) {
        var itemCopy = angular.copy(item);
        itemCopy.equipped = true;
        finalItems[type.toLowerCase()] = [ itemCopy ];
      });

      return {
        classType: -1,
        name: name,
        items: finalItems
      };
    }
  }
})();
<commit_msg>Better filtering of loadout to equipped<commit_after>(function() {
  'use strict';
  angular.module('dimApp')
    .directive('dimLoadoutPopup', LoadoutPopup);

  LoadoutPopup.$inject = [];

  function LoadoutPopup() {
    return {
      controller: LoadoutPopupCtrl,
      controllerAs: 'vm',
      bindToController: true,
      restrict: 'A',
      scope: {
        store: '=dimLoadoutPopup'
      },
      replace: true,
      template: [
        '
',
        '
',
        '
',
        '      + Create Loadout',
        '      From Equipped',
        '
',
        '
',
        '      {{ loadout.name }}',
        '      ',
        '      ',
        '
',
        '
',
        '       Maximize Light',
        '
',
        '
',
        '       Item Leveling',
        '
',
        '
',
        '       Gather Engrams',
        '
',
        '
',
        '       Engrams to Vault',
        '
',
        '
',
        '       {{vm.previousLoadout.name}}',
        '      All items',
        '
',
        '
',
        '
'
      ].join('')
    };
  }

  LoadoutPopupCtrl.$inject = ['$rootScope', 'ngDialog', 'dimLoadoutService', 'dimItemService', 'dimItemTier', 'toaster', 'dimEngramFarmingService'];

  function LoadoutPopupCtrl($rootScope, ngDialog, dimLoadoutService, dimItemService, dimItemTier, toaster, dimEngramFarmingService) {
    var vm = this;
    vm.previousLoadout = dimLoadoutService.previousLoadouts[vm.store.id];

    vm.classTypeId = {
      'warlock': 0,
      'titan': 1,
      'hunter': 2
    }[vm.store.class] || -1;

    function initLoadouts() {
      dimLoadoutService.getLoadouts()
        .then(function(loadouts) {
          vm.loadouts = _.sortBy(loadouts, 'name') || [];

          vm.loadouts = _.filter(vm.loadouts, function(item) {
            return vm.classTypeId === -1 || ((item.classType === -1) || (item.classType === vm.classTypeId));
          });
        });
    }
    $rootScope.$on('dim-save-loadout', initLoadouts);
    $rootScope.$on('dim-delete-loadout', initLoadouts);
    initLoadouts();

    vm.newLoadout = function newLoadout($event) {
      ngDialog.closeAll();
      $rootScope.$broadcast('dim-create-new-loadout', { });
    };

    vm.newLoadoutFromEquipped = function newLoadout($event) {
      ngDialog.closeAll();

      var loadout = filterLoadoutToEquipped(loadoutFromCurrentlyEquipped(vm.store.items, ""));
      // We don't want to prepopulate the loadout with a bunch of cosmetic junk
      // like emblems and ships and horns.
      loadout.items = _.pick(loadout.items,
                             'class',
                             'primary',
                             'special',
                             'heavy',
                             'helmet',
                             'gauntlets',
                             'chest',
                             'leg',
                             'classitem',
                             'artifact',
                             'ghost');
      loadout.classType = vm.classTypeId;
      vm.editLoadout(loadout, $event);
    };

    vm.deleteLoadout = function deleteLoadout(loadout, $event) {
      if (confirm("Are you sure you want to delete '" + loadout.name + "'?")) {
        dimLoadoutService.deleteLoadout(loadout);
      }
    };

    vm.editLoadout = function editLoadout(loadout, $event) {
      ngDialog.closeAll();
      $rootScope.$broadcast('dim-edit-loadout', {
        loadout: loadout
      });
    };

    function loadoutFromCurrentlyEquipped(items, name) {
      return {
        classType: -1,
        name: name,
        items: _(items)
          .chain()
          .select(function(item) {
            return item.canBeInLoadout();
          })
          .map(function (i) {
            return angular.copy(i);
          })
          .groupBy(function(i) {
            return i.type.toLowerCase();
          }).value()
      };
    }

    function filterLoadoutToEquipped(loadout) {
      var filteredLoadout = angular.copy(loadout);
      filteredLoadout.items = _.mapObject(filteredLoadout.items, function(items) {
        return _.select(items, 'equipped');
      });
      return filteredLoadout;
    }

    vm.applyLoadout = function applyLoadout(loadout, $event, filterToEquipped) {
      ngDialog.closeAll();
      dimEngramFarmingService.stop();

      if (!vm.store.isVault) {
        if (loadout === vm.previousLoadout) {
          vm.previousLoadout = undefined;
        } else {
          vm.previousLoadout = loadoutFromCurrentlyEquipped(vm.store.items, 'Before "' + loadout.name + '"');
        }
        dimLoadoutService.previousLoadouts[vm.store.id] = vm.previousLoadout; // ugly hack
      }

      if (filterToEquipped) {
        loadout = filterLoadoutToEquipped(loadout);
      }

      dimLoadoutService.applyLoadout(vm.store, loadout);
    };

    // A dynamic loadout set up to level weapons and armor
    vm.itemLevelingLoadout = function itemLevelingLoadout($event) {
      var applicableItems = _.select(dimItemService.getItems(), function(i) {
        return i.canBeEquippedBy(vm.store) &&
          i.talentGrid && !i.talentGrid.xpComplete; // Still need XP
      });

      var bestItemFn = function(item) {
        // Leave equipped items alone if they need XP
        if (item.equipped) {
          return 1000;
        }

        var value = 0;
        // Prefer locked items (they're stuff you want to use/keep)
        // and apply different rules to them.
        if (item.locked) {
          value += 500;
          value += [
            'Common',
            'Uncommon',
            'Rare',
            'Legendary',
            'Exotic'
          ].indexOf(item.tier) * 10;
        } else {
          // For unlocked, prefer blue items so when you destroy them you get more mats.
          value += [
            'Common',
            'Uncommon',
            'Exotic',
            'Legendary',
            'Rare'
          ].indexOf(item.tier) * 10;
        }

        // Choose the item w/ the highest XP
        value += 10 * (item.talentGrid.totalXP / item.talentGrid.totalXPRequired);

        if (item.owner == vm.store.id) {
          // Prefer items owned by this character
          value += 0.5;
        } else if (item.owner == 'vault') {
          // Prefer items in the vault over items owned by a different character
          // (but not as much as items owned by this character)
          value += 0.05;
        }

        value += item.primStat ? item.primStat.value / 1000 : 0;

        return value;
      };

      var loadout = optimalLoadout(applicableItems, bestItemFn, 'Item Leveling');
      vm.applyLoadout(loadout, $event);
    };

    // Apply a loadout that's dynamically calculated to maximize Light level (preferring not to change currently-equipped items)
    vm.maxLightLoadout = function maxLightLoadout($event) {
      // These types contribute to light level
      var lightTypes = ['Primary',
                        'Special',
                        'Heavy',
                        'Helmet',
                        'Gauntlets',
                        'Chest',
                        'Leg',
                        'ClassItem',
                        'Artifact',
                        'Ghost'];

      var applicableItems = _.select(dimItemService.getItems(), function(i) {
        return i.canBeEquippedBy(vm.store) &&
          i.primStat !== undefined && // has a primary stat (sanity check)
          _.contains(lightTypes, i.type); // one of our selected types
      });

      var bestItemFn = function(item) {
        var value = item.primStat.value;

        // Break ties when items have the same stats. Note that this should only
        // add less than 0.25 total, since in the exotics special case there can be
        // three items in consideration and you don't want to go over 1 total.
        if (item.owner == vm.store.id) {
          // Prefer items owned by this character
          value += 0.1;
          if (item.equipped) {
            // Prefer them even more if they're already equipped
            value += 0.1;
          }
        } else if (item.owner == 'vault') {
          // Prefer items in the vault over items owned by a different character
          // (but not as much as items owned by this character)
          value += 0.05;
        }
        return value;
      };

      var loadout = optimalLoadout(applicableItems, bestItemFn, 'Maximize Light');
      vm.applyLoadout(loadout, $event);
    };

    // A dynamic loadout set up to level weapons and armor
    vm.gatherEngramsLoadout = function gatherEngramsLoadout($event) {
      var engrams = _.select(dimItemService.getItems(), function(i) {
        return i.isEngram() && i.sort !== 'Postmaster';
      });

      if (engrams.length === 0) {
        toaster.pop('warning', 'Gather Engrams', 'No engrams are available to transfer.');
        return;
      }

      var itemsByType = _.mapObject(_.groupBy(engrams, 'type'), function(items, type) {
        // No more than 9 engrams of a type
        return _.first(items, 9);
      });

      // Copy the items and mark them equipped and put them in arrays, so they look like a loadout
      var finalItems = {};
      _.each(itemsByType, function(items, type) {
        if (items) {
          finalItems[type.toLowerCase()] = items.map(function(i) {
            return angular.copy(i);
          });
        }
      });

      var loadout = {
        classType: -1,
        name: 'Gather Engrams',
        items: finalItems
      };
      vm.applyLoadout(loadout, $event);
    };

    vm.startFarmingEngrams = function startFarmingEngrams($event) {
      ngDialog.closeAll();
      dimEngramFarmingService.start(vm.store);
    };

    // Generate an optimized loadout based on a filtered set of items and a value function
    function optimalLoadout(applicableItems, bestItemFn, name) {
      var itemsByType = _.groupBy(applicableItems, 'type');

      var isExotic = function(item) {
        return item.tier === dimItemTier.exotic;
      };

      // Pick the best item
      var items = _.mapObject(itemsByType, function(items, type) {
        return _.max(items, bestItemFn);
      });

      // Solve for the case where our optimizer decided to equip two exotics
      var exoticGroups = [ ['Primary', 'Special', 'Heavy'], ['Helmet', 'Gauntlets', 'Chest', 'Leg'] ];
      _.each(exoticGroups, function(group) {
        var itemsInGroup = _.pick(items, group);
        var numExotics = _.select(_.values(itemsInGroup), isExotic).length;
        if (numExotics > 1) {
          var options = [];

          // Generate an option where we use each exotic
          _.each(itemsInGroup, function(item, type) {
            if (isExotic(item)) {
              var option = angular.copy(itemsInGroup);
              var optionValid = true;
              // Switch the other exotic items to the next best non-exotic
              _.each(_.omit(itemsInGroup, type), function(otherItem, otherType) {
                if (isExotic(otherItem)) {
                  var nonExotics = _.reject(itemsByType[otherType], isExotic);
                  if (_.isEmpty(nonExotics)) {
                    // this option isn't usable because we couldn't swap this exotic for any non-exotic
                    optionValid = false;
                  } else {
                    option[otherType] = _.max(nonExotics, bestItemFn);
                  }
                }
              });

              if (optionValid) {
                options.push(option);
              }
            }
          });

          // Pick the option where the optimizer function adds up to the biggest number, again favoring equipped stuff
          var bestOption = _.max(options, function(opt) { return sum(_.values(opt), bestItemFn); });
          _.assign(items, bestOption);
        }
      });

      // Copy the items and mark them equipped and put them in arrays, so they look like a loadout
      var finalItems = {};
      _.each(items, function(item, type) {
        var itemCopy = angular.copy(item);
        itemCopy.equipped = true;
        finalItems[type.toLowerCase()] = [ itemCopy ];
      });

      return {
        classType: -1,
        name: name,
        items: finalItems
      };
    }
  }
})();
<|endoftext|>


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

'use strict';

async function main(
  project = 'projects/my-project/locations/-', // Get the functions for a project.
  page_size = 1, //Max number of functions to return per call
  page_token = 'token'
) {
  // [START cloud_nodejs_functions_quickstart]
  // Imports the Google Cloud client library

  // remove this line after package is released
  // eslint-disable-next-line node/no-missing-require
  const {CloudFunctionsServiceClient} = require('@google-cloud/functions');

  // TODO(developer): replace with your prefered project ID.
  // const projectId = 'my-project'

  // Creates a client
  const client = new CloudFunctionsServiceClient();

  // project = 'my-project/*/locations/*' // Get the functions for a project.
  async function listFunctions() {
    const [functions] = await client.listFunctions({
      parent: project,
      pageSize: page_size,
      pageToken: page_token,
    });
    console.info(functions);
  }
  listFunctions();
  // [END cloud_nodejs_functions_quickstart]
}

main(...process.argv.slice(2)).catch(err => {
  console.error(err.message);
  process.exitCode = 1;
});
process.on('unhandledRejection', err => {
  console.error(err.message);
  process.exitCode = 1;
});
<commit_msg>fix: space<commit_after>// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

'use strict';

async function main(
  project = 'projects/my-project/locations/-', // Get the functions for a project.
  page_size = 1, //Max number of functions to return per call
  page_token = 'token'
) {
  // [START cloud_nodejs_functions_quickstart]
  // Imports the Google Cloud client library

  // remove this line after package is released
  // eslint-disable-next-line node/no-missing-require
  const {CloudFunctionsServiceClient} = require('@google-cloud/functions');

  // TODO(developer): replace with your prefered project ID.
  // const projectId = 'my-project'

  // Creates a client
  const client = new CloudFunctionsServiceClient();

  // project = 'my-project/*/locations/*' // Get the functions for a project.
  async function listFunctions() {
    const [functions] = await client.listFunctions({
      parent: project,
      pageSize: page_size,
      pageToken: page_token,
    });
    console.info(functions);
  }
  listFunctions();
  // [END cloud_nodejs_functions_quickstart]
}

main(...process.argv.slice(2)).catch(err => {
  console.error(err.message);
  process.exitCode = 1;
});
process.on('unhandledRejection', err => {
  console.error(err.message);
  process.exitCode = 1;
});
<|endoftext|>


<commit_before>import React from 'react';
import {
	View,
	Text,
	ListView,
	StyleSheet,
} from 'react-native';
import { connect } from 'react-redux';
import moment from 'moment';

import {
	MAX_CARD_WIDTH,
	WINDOW_WIDTH,
	WINDOW_HEIGHT,
	NAVIGATOR_HEIGHT,
	TAB_BAR_HEIGHT,
} from '../../styles/LayoutConstants';
import EmptyItem from './EmptyItem';
import SpecialEventsItem from './SpecialEventsItem';
import SpecialEventsHeader from './SpecialEventsHeader';

const dataSource = new ListView.DataSource({
	rowHasChanged: (r1, r2) => r1 !== r2,
	sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});

const SpecialEventsListView = ({ addSpecialEvents, specialEventsSchedule,
	specialEventsScheduleIds, removeSpecialEvents, saved,disabled, personal, rows,
	scrollEnabled, style, labels, labelItemIds, selectedDay, days, daysItemIds, inCard }) => {

	let scheduleIdArray = [];
	// Use ids from selectedDay
	if (daysItemIds) {
		if (!selectedDay) {
			// Select current day by default
			for (let i = 0; i < days.length; ++i) {
				selectedDay = days[i];
				if (moment(selectedDay).isSameOrAfter(moment(), 'day')) {
					scheduleIdArray = daysItemIds[selectedDay];

					// Filter saved for day
					if (personal && Array.isArray(saved)) {
						scheduleIdArray = scheduleIdArray.filter((item) => saved.includes(item));
					}

					// If displaying for card...continue looking for a day with saved
					if (inCard && scheduleIdArray.length === 0) {
						// continue
					} else {
						break;
					}
				}
			}
		}
		scheduleIdArray = daysItemIds[selectedDay];

		// Filter saved for day
		if (personal && Array.isArray(saved)) {
			scheduleIdArray = scheduleIdArray.filter((item) => saved.includes(item));
		}

		// Apply label filtering
		if (!personal && labels.length > 0) {
			let labelArray = [];
			for (let j = 0; i < labels.length; ++j) {
				const label = labels[j];
				const items = labelItemIds[label];

				labelArray = labelArray.concat(items);
			}
			scheduleIdArray = scheduleIdArray.filter((item) => labelArray.includes(item));
		}
	}
	if (personal && scheduleIdArray.length === 0) {
		return (
			<View style={[style, rows ? styles.card : styles.full]}>
				<Text style={styles.noSavedSessions}>
					Click the star icon next to a session to save it to your list.
				</Text>
			</View>
		);
	} else {
		return (
			<ListView
				style={[style, rows ? styles.card : styles.full]}
				scrollEnabled={scrollEnabled}
				stickySectionHeadersEnabled={false}
				dataSource={
					dataSource.cloneWithRowsAndSections(
						convertToTimeMap(
							specialEventsSchedule,
							adjustData(specialEventsSchedule, scheduleIdArray, saved, personal, rows)
						)
					)
				}
				renderRow={(rowData, sectionID, rowID, highlightRow) => {
					// Don't render first row bc rendered by header
					if (Number(rowID) !== 0) {
						return (
							<View style={styles.rowContainer}>
								<EmptyItem />
								<SpecialEventsItem
									specialEventsData={rowData}
									saved={saved.includes(rowData.id)}
									add={(disabled) ? null : addSpecialEvents}
									remove={removeSpecialEvents}
								/>
							</View>
						);
					} else {
						return null;
					}
				}}
				renderSectionHeader={(sectionData, sectionID) => (
					// Render header along with first row
					<View style={styles.rowContainer}>
						<SpecialEventsHeader
							timestamp={sectionID}
						/>
						<SpecialEventsItem
							specialEventsData={sectionData[0]}
							saved={saved.includes(sectionData[0].id)}
							add={(disabled) ? null : addSpecialEvents}
							remove={removeSpecialEvents}
						/>
					</View>
				)}
			/>
		);
	}
};

/*
	Filters what session ids to use based on personal/saved and/or rows
	@returns Array of session ids
 */
function adjustData(scheduleIdMap, scheduleIdArray, savedArray, personal, rows) {
	// Filter out saved items
	if (!personal || !Array.isArray(savedArray)) {
		const keys = scheduleIdArray;
		if (!rows) {
			return scheduleIdArray;
		} else {
			const trimmed = [];

			for (let i = 0; i < rows; ++i) {
				trimmed.push(keys[i]);
			}
			return trimmed;
		}
	} else {
		let filtered = [];
		// Check if saved item is part of ids to be displayed
		for (let i = 0; i < savedArray.length; ++i) {
			const key = savedArray[i];
			if (scheduleIdArray.includes(key)) {
				filtered.push(key);
			}
		}

		// Displaying for homecard
		// Remove sessions that have passed
		if (rows) {
			const now = Date.now();
			if (filtered.length > rows) {
				const temp = filtered.slice();
				for (let j = 0; j < filtered.length; ++j) {
					const key = filtered[j];
					const itemTime = scheduleIdMap[key]['start-time'];

					if (now > itemTime) {
						const index = temp.indexOf(key);
						temp.splice(index, 1);
					}

					if (temp.length <= rows) {
						break;
					}
				}

				if (temp.length > rows) {
					filtered = temp.slice(0, rows);
				} else {
					filtered = temp;
				}
			}
		}
		return filtered;
	}
}

/*
	@returns Object that maps keys from scheduleArray to Objects in scheduleMap
*/
function convertToTimeMap(scheduleMap, scheduleArray, header = false) {
	const timeMap = {};

	if (Array.isArray(scheduleArray)) {
		scheduleArray.forEach((key) => {
			const session = scheduleMap[key];
			if (!timeMap[session['start-time']]) {
				// Create an entry in the map for the timestamp if it hasn't yet been created
				timeMap[session['start-time']] = [];
			}
			timeMap[session['start-time']].push(session);
		});

		// Remove an item from section so spacing lines up
		if (header) {
			Object.keys(timeMap).forEach((key) => {
				timeMap[key].pop();
			});
		}
	}
	return timeMap;
}

const mapStateToProps = (state) => (
	{
		specialEventsSchedule: state.specialEvents.data.schedule,
		specialEventsScheduleIds: state.specialEvents.data.uids,
		saved: state.specialEvents.saved,
		labelItemIds: state.specialEvents.data['label-items'],
		labels: state.specialEvents.labels,
		days: state.specialEvents.data.dates,
		daysItemIds: state.specialEvents.data['date-items'],
	}
);

const mapDispatchToProps = (dispatch) => (
	{
		addSpecialEvents: (id) => {
			dispatch({ type: 'ADD_SPECIAL_EVENTS', id });
		},
		removeSpecialEvents: (id) => {
			dispatch({ type: 'REMOVE_SPECIAL_EVENTS', id });
		}
	}
);

const ActualSpecialEventsListView = connect(
	mapStateToProps,
	mapDispatchToProps
)(SpecialEventsListView);

const styles = StyleSheet.create({
	rowContainer: { flexDirection: 'row', height: 76 },
	full: { width: WINDOW_WIDTH, height: (WINDOW_HEIGHT - NAVIGATOR_HEIGHT - TAB_BAR_HEIGHT) },
	card: { width: MAX_CARD_WIDTH },
	noSavedSessions: { flexGrow: 1, fontSize: 18, textAlign: 'center', padding: 40, lineHeight: 30 },
});

export default ActualSpecialEventsListView;
<commit_msg>Use correct var in for loop<commit_after>import React from 'react';
import {
	View,
	Text,
	ListView,
	StyleSheet,
} from 'react-native';
import { connect } from 'react-redux';
import moment from 'moment';

import {
	MAX_CARD_WIDTH,
	WINDOW_WIDTH,
	WINDOW_HEIGHT,
	NAVIGATOR_HEIGHT,
	TAB_BAR_HEIGHT,
} from '../../styles/LayoutConstants';
import EmptyItem from './EmptyItem';
import SpecialEventsItem from './SpecialEventsItem';
import SpecialEventsHeader from './SpecialEventsHeader';

const dataSource = new ListView.DataSource({
	rowHasChanged: (r1, r2) => r1 !== r2,
	sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});

const SpecialEventsListView = ({ addSpecialEvents, specialEventsSchedule,
	specialEventsScheduleIds, removeSpecialEvents, saved,disabled, personal, rows,
	scrollEnabled, style, labels, labelItemIds, selectedDay, days, daysItemIds, inCard }) => {

	let scheduleIdArray = [];
	// Use ids from selectedDay
	if (daysItemIds) {
		if (!selectedDay) {
			// Select current day by default
			for (let i = 0; i < days.length; ++i) {
				selectedDay = days[i];
				if (moment(selectedDay).isSameOrAfter(moment(), 'day')) {
					scheduleIdArray = daysItemIds[selectedDay];

					// Filter saved for day
					if (personal && Array.isArray(saved)) {
						scheduleIdArray = scheduleIdArray.filter((item) => saved.includes(item));
					}

					// If displaying for card...continue looking for a day with saved
					if (inCard && scheduleIdArray.length === 0) {
						// continue
					} else {
						break;
					}
				}
			}
		}
		scheduleIdArray = daysItemIds[selectedDay];

		// Filter saved for day
		if (personal && Array.isArray(saved)) {
			scheduleIdArray = scheduleIdArray.filter((item) => saved.includes(item));
		}

		// Apply label filtering
		if (!personal && labels.length > 0) {
			let labelArray = [];
			for (let j = 0; j < labels.length; ++j) {
				const label = labels[j];
				const items = labelItemIds[label];

				labelArray = labelArray.concat(items);
			}
			scheduleIdArray = scheduleIdArray.filter((item) => labelArray.includes(item));
		}
	}
	if (personal && scheduleIdArray.length === 0) {
		return (
			<View style={[style, rows ? styles.card : styles.full]}>
				<Text style={styles.noSavedSessions}>
					Click the star icon next to a session to save it to your list.
				</Text>
			</View>
		);
	} else {
		return (
			<ListView
				style={[style, rows ? styles.card : styles.full]}
				scrollEnabled={scrollEnabled}
				stickySectionHeadersEnabled={false}
				dataSource={
					dataSource.cloneWithRowsAndSections(
						convertToTimeMap(
							specialEventsSchedule,
							adjustData(specialEventsSchedule, scheduleIdArray, saved, personal, rows)
						)
					)
				}
				renderRow={(rowData, sectionID, rowID, highlightRow) => {
					// Don't render first row bc rendered by header
					if (Number(rowID) !== 0) {
						return (
							<View style={styles.rowContainer}>
								<EmptyItem />
								<SpecialEventsItem
									specialEventsData={rowData}
									saved={saved.includes(rowData.id)}
									add={(disabled) ? null : addSpecialEvents}
									remove={removeSpecialEvents}
								/>
							</View>
						);
					} else {
						return null;
					}
				}}
				renderSectionHeader={(sectionData, sectionID) => (
					// Render header along with first row
					<View style={styles.rowContainer}>
						<SpecialEventsHeader
							timestamp={sectionID}
						/>
						<SpecialEventsItem
							specialEventsData={sectionData[0]}
							saved={saved.includes(sectionData[0].id)}
							add={(disabled) ? null : addSpecialEvents}
							remove={removeSpecialEvents}
						/>
					</View>
				)}
			/>
		);
	}
};

/*
	Filters what session ids to use based on personal/saved and/or rows
	@returns Array of session ids
 */
function adjustData(scheduleIdMap, scheduleIdArray, savedArray, personal, rows) {
	// Filter out saved items
	if (!personal || !Array.isArray(savedArray)) {
		const keys = scheduleIdArray;
		if (!rows) {
			return scheduleIdArray;
		} else {
			const trimmed = [];

			for (let i = 0; i < rows; ++i) {
				trimmed.push(keys[i]);
			}
			return trimmed;
		}
	} else {
		let filtered = [];
		// Check if saved item is part of ids to be displayed
		for (let i = 0; i < savedArray.length; ++i) {
			const key = savedArray[i];
			if (scheduleIdArray.includes(key)) {
				filtered.push(key);
			}
		}

		// Displaying for homecard
		// Remove sessions that have passed
		if (rows) {
			const now = Date.now();
			if (filtered.length > rows) {
				const temp = filtered.slice();
				for (let j = 0; j < filtered.length; ++j) {
					const key = filtered[j];
					const itemTime = scheduleIdMap[key]['start-time'];

					if (now > itemTime) {
						const index = temp.indexOf(key);
						temp.splice(index, 1);
					}

					if (temp.length <= rows) {
						break;
					}
				}

				if (temp.length > rows) {
					filtered = temp.slice(0, rows);
				} else {
					filtered = temp;
				}
			}
		}
		return filtered;
	}
}

/*
	@returns Object that maps keys from scheduleArray to Objects in scheduleMap
*/
function convertToTimeMap(scheduleMap, scheduleArray, header = false) {
	const timeMap = {};

	if (Array.isArray(scheduleArray)) {
		scheduleArray.forEach((key) => {
			const session = scheduleMap[key];
			if (!timeMap[session['start-time']]) {
				// Create an entry in the map for the timestamp if it hasn't yet been created
				timeMap[session['start-time']] = [];
			}
			timeMap[session['start-time']].push(session);
		});

		// Remove an item from section so spacing lines up
		if (header) {
			Object.keys(timeMap).forEach((key) => {
				timeMap[key].pop();
			});
		}
	}
	return timeMap;
}

const mapStateToProps = (state) => (
	{
		specialEventsSchedule: state.specialEvents.data.schedule,
		specialEventsScheduleIds: state.specialEvents.data.uids,
		saved: state.specialEvents.saved,
		labelItemIds: state.specialEvents.data['label-items'],
		labels: state.specialEvents.labels,
		days: state.specialEvents.data.dates,
		daysItemIds: state.specialEvents.data['date-items'],
	}
);

const mapDispatchToProps = (dispatch) => (
	{
		addSpecialEvents: (id) => {
			dispatch({ type: 'ADD_SPECIAL_EVENTS', id });
		},
		removeSpecialEvents: (id) => {
			dispatch({ type: 'REMOVE_SPECIAL_EVENTS', id });
		}
	}
);

const ActualSpecialEventsListView = connect(
	mapStateToProps,
	mapDispatchToProps
)(SpecialEventsListView);

const styles = StyleSheet.create({
	rowContainer: { flexDirection: 'row', height: 76 },
	full: { width: WINDOW_WIDTH, height: (WINDOW_HEIGHT - NAVIGATOR_HEIGHT - TAB_BAR_HEIGHT) },
	card: { width: MAX_CARD_WIDTH },
	noSavedSessions: { flexGrow: 1, fontSize: 18, textAlign: 'center', padding: 40, lineHeight: 30 },
});

export default ActualSpecialEventsListView;
<|endoftext|>


<commit_before>(function()
{
  angular
    .module('leaderboard')
    .controller('LeaderboardController', LeaderboardController);

  LeaderboardController.$inject = ['$scope', '$state', 'localStorage', '$ionicSlideBoxDelegate', 'FooseyService', 'SettingsService', 'BadgesService'];

  function LeaderboardController($scope, $state, localStorage, $ionicSlideBoxDelegate, FooseyService, SettingsService, BadgesService)
  {
    $scope.settings = SettingsService;
    $scope.badges = BadgesService;
    $scope.slide = 0;
    $scope.loading = true;
    $scope.minimumQualified = 10;
    $scope.elos = undefined;
    $scope.winRates = undefined;

    $scope.updateLeaderboard = updateLeaderboard;
    $scope.changeSlide = changeSlide;
    $scope.slideTo = slideTo;

    // load on entering view
    $scope.$on('$ionicView.beforeEnter', function()
    {
      // send to login screen if they haven't logged in yet
      if (!SettingsService.loggedIn) SettingsService.reallyLogOut();
      updateLeaderboard();
    });

    // function for swiping between views
    function changeSlide(index)
    {
      $scope.slide = index;
    }

    function slideTo(index)
    {
      $ionicSlideBoxDelegate.slide(index);
    }

    function updateLeaderboard()
    {
      BadgesService.updateBadges();
      getStats();
    }

    // gets the list of names and elos
    function getStats()
    {
      // load from local storage
      $scope.elos = localStorage.getObject('elos');
      $scope.winRates = localStorage.getObject('winRates');
      $scope.loading = true;

      // load from server
      FooseyService.getAllPlayers(true).then(
        function successCallback(players)
        {
          // Remove people from the leaderboard who haven't played or are inactive
          if (!SettingsService.noGamePlayers) players = _.filter(players, hasPlayed)

          $scope.elos = getEloRank(players);
          $scope.winRates = players.sort(sortWinRate);
          $ionicSlideBoxDelegate.update();
          localStorage.setObject('elos', $scope.elos);
          localStorage.setObject('winRates', $scope.winRates);
          $scope.error = false;

          done();
        },
        function errorCallback(response)
        {
          $scope.error = true;
          done();
        });
    }

    // filters out people that have not yet played enough games
    function getEloRank(players)
    {
      var eloRank = [];
      var unranked = [];
      var rank = 1;

      players.sort(sortElos);

      // set rank and if they're qualified
      for (var i = 0; i < players.length; i++)
      {
        if (players[i].gamesPlayed >= $scope.minimumQualified)
        {
          // same rank if same elo
          if (i > 0 && players[i].elo === _.last(eloRank).elo)
            players[i].rank = _.last(eloRank).rank;
          else
            players[i].rank = rank;

          players[i].qualified = true;
          rank++;
          eloRank.push(players[i]);
        }
        else
        {
          players[i].disqualified = true;
          players[i].rank = '-';
          unranked.push(players[i]);
        }
      }

      // add unranked to bottom
      for (var i = 0; i < unranked.length; i++)
      {
        eloRank.push(unranked[i]);
      }

      return eloRank;
    }

    function hasPlayed(player)
    {
      return player.gamesPlayed > 0;
    }

    function sortElos(a, b)
    {
      return b.elo - a.elo;
    }

    function sortWinRate(a, b)
    {
      return b.winRate - a.winRate;
    }

    // turns off spinner and notifies
    function done()
    {
      $scope.loading = false;
      $scope.$broadcast('scroll.refreshComplete');
    }
  }
})();<commit_msg>Fixed a leaderboard sorting bug<commit_after>(function()
{
  angular
    .module('leaderboard')
    .controller('LeaderboardController', LeaderboardController);

  LeaderboardController.$inject = ['$scope', '$state', 'localStorage', '$ionicSlideBoxDelegate', 'FooseyService', 'SettingsService', 'BadgesService'];

  function LeaderboardController($scope, $state, localStorage, $ionicSlideBoxDelegate, FooseyService, SettingsService, BadgesService)
  {
    $scope.settings = SettingsService;
    $scope.badges = BadgesService;
    $scope.slide = 0;
    $scope.loading = true;
    $scope.minimumQualified = 10;
    $scope.elos = undefined;
    $scope.winRates = undefined;

    $scope.updateLeaderboard = updateLeaderboard;
    $scope.changeSlide = changeSlide;
    $scope.slideTo = slideTo;

    // load on entering view
    $scope.$on('$ionicView.beforeEnter', function()
    {
      // send to login screen if they haven't logged in yet
      if (!SettingsService.loggedIn) SettingsService.reallyLogOut();
      updateLeaderboard();
    });

    // function for swiping between views
    function changeSlide(index)
    {
      $scope.slide = index;
    }

    function slideTo(index)
    {
      $ionicSlideBoxDelegate.slide(index);
    }

    function updateLeaderboard()
    {
      BadgesService.updateBadges();
      getStats();
    }

    // gets the list of names and elos
    function getStats()
    {
      // load from local storage
      $scope.elos = localStorage.getObject('elos');
      $scope.winRates = localStorage.getObject('winRates');
      $scope.loading = true;

      // load from server
      FooseyService.getAllPlayers(true).then(
        function successCallback(players)
        {
          // Remove people from the leaderboard who haven't played or are inactive
          if (!SettingsService.noGamePlayers) players = _.filter(players, hasPlayed)

          $scope.elos = getEloRank(players);
          $scope.winRates = players.sort(sortWinRate);
          $ionicSlideBoxDelegate.update();
          localStorage.setObject('elos', $scope.elos);
          localStorage.setObject('winRates', $scope.winRates);
          $scope.error = false;

          done();
        },
        function errorCallback(response)
        {
          $scope.error = true;
          done();
        });
    }

    // filters out people that have not yet played enough games
    function getEloRank(players)
    {
      var eloRank = [];
      var unranked = [];
      var rank = 1;

      players.sort(sortElos);

      // set rank and if they're qualified
      for (var i = 0; i < players.length; i++)
      {
        if (players[i].gamesPlayed >= $scope.minimumQualified)
        {
          // same rank if same elo
          if (eloRank.length > 0 && players[i].elo === _.last(eloRank).elo)
            players[i].rank = _.last(eloRank).rank;
          else
            players[i].rank = rank;

          players[i].qualified = true;
          rank++;
          eloRank.push(players[i]);
        }
        else
        {
          players[i].disqualified = true;
          players[i].rank = '-';
          unranked.push(players[i]);
        }
      }

      // add unranked to bottom
      for (var i = 0; i < unranked.length; i++)
      {
        eloRank.push(unranked[i]);
      }

      return eloRank;
    }

    function hasPlayed(player)
    {
      return player.gamesPlayed > 0;
    }

    function sortElos(a, b)
    {
      return b.elo - a.elo;
    }

    function sortWinRate(a, b)
    {
      return b.winRate - a.winRate;
    }

    // turns off spinner and notifies
    function done()
    {
      $scope.loading = false;
      $scope.$broadcast('scroll.refreshComplete');
    }
  }
})();
<|endoftext|>