<commit_before>import Realm from 'realm';
import {
  addBatchToParent,
  createRecord,
  getTotal,
  reuseNumber as reuseSerialNumber,
} from '../utilities';
import { SERIAL_NUMBER_KEYS } from '../index';
import { complement } from 'set-manipulator';

export class Transaction extends Realm.Object {
  constructor() {
    super();
    this.addItemsFromMasterList = this.addItemsFromMasterList.bind(this);
  }

  destructor(database) {
    if (this.isFinalised) throw new Error('Cannot delete finalised transaction');
    if (this.isCustomerInvoice) {
      reuseSerialNumber(database, SERIAL_NUMBER_KEYS.CUSTOMER_INVOICE, this.serialNumber);
    }
    database.delete('TransactionItem', this.items);
  }
  // Is external supplier invoice
  get isExternalSupplierInvoice() {
    return this.isSupplierInvoice && this.otherParty && this.otherParty.isExternalSupplier;
  }

  get isFinalised() {
    return this.status === 'finalised';
  }

  get isConfirmed() {
    return this.status === 'confirmed';
  }

  get isIncoming() {
    return this.type === 'supplier_invoice';
  }

  get isOutgoing() {
    return this.type === 'customer_invoice' || this.type === 'supplier_credit';
  }

  get isCustomerInvoice() {
    return this.type === 'customer_invoice';
  }

  get isSupplierInvoice() {
    return this.type === 'supplier_invoice';
  }

  get isInventoryAdjustment() {
    return this.otherParty && this.otherParty.type === 'inventory_adjustment';
  }

  get otherPartyName() {
    return this.otherParty ? this.otherParty.name : '';
  }

  get totalPrice() {
    return getTotal(this.items, 'totalPrice');
  }

  get totalQuantity() {
    return getTotal(this.items, 'totalQuantity');
  }

  get numberOfBatches() {
    return getTotal(this.items, 'numberOfBatches');
  }

  get numberOfItems() {
    return this.items.length;
  }

  hasItemWithId(itemId) {
    return this.items.filtered('item.id == $0', itemId).length > 0;
  }

  /**
   * Add a TransactionItem to this transaction, based on the given item. If it already
   * exists, do nothing.
   * @param {object} transactionItem  The TransactionItem to add
   */
  addItem(transactionItem) {
    this.items.push(transactionItem);
  }

  /**
   * Add all items from the customer's master list to this customer invoice
   */
  addItemsFromMasterList(database) {
    if (!this.isCustomerInvoice) throw new Error(`Cannot add master lists to ${this.type}`);
    if (this.isFinalised) throw new Error('Cannot add items to a finalised transaction');
    if (this.otherParty) {
      this.otherParty.masterLists.forEach(masterList => {
        const itemsToAdd = complement(masterList.items, this.items, item => item.itemId);
        itemsToAdd.forEach(masterListItem =>
          createRecord(database, 'TransactionItem', this, masterListItem.item)
        );
      });
    }
  }

  get isLinkedToRequisition() {
    return !(!this.linkedRequisition);
  }

  /**
   * Remove the transaction items with the given ids from this transaction
   * @param  {Realm}  database        App wide local database
   * @param  {array}  itemIds         The ids of transactionItems to remove
   * @return {none}
   */
  removeItemsById(database, itemIds) {
    const itemsToDelete = [];
    for (let i = 0; i < itemIds.length; i++) {
      const transactionItem = this.items.find(testItem => testItem.id === itemIds[i]);
      if (transactionItem.isValid()) {
        itemsToDelete.push(transactionItem);
      }
    }
    database.delete('transactionItem', itemsToDelete);
  }

  /**
   * Remove transaction batches with given ids from this transaction, then prune any
   * TransactionItems that are emptied entirely of batches
   * @param  {Realm}  database  App wide local database
   * @param  {array}  transactionBatchIds The ids of transactionBatches to remove
   * @return {none}
   */
  removeTransactionBatchesById(database, transactionBatchIds) {
    if (this.isFinalised) throw new Error('Cannot modify finalised transaction');
    const transactionBatches = this.getTransactionBatches(database);
    const transactionBatchesToDelete = [];
    transactionBatchIds.forEach(transactionBatchId => {
      const transactionBatch = transactionBatches.find(matchTransactionBatch =>
                                  matchTransactionBatch.id === transactionBatchId);
      transactionBatchesToDelete.push(transactionBatch);
    });
    database.delete('TransactionBatch', transactionBatchesToDelete);
    this.pruneBatchlessTransactionItems(database);
  }

  /**
   * Remove the transaction item passed as param
   * @param  {Realm}  database        App wide local database
   * @param  {TransactionItem}  TransactionItem to remove
   * @return {none}
   */
  removeTransactionItem(database, transactionItem) {
    database.delete('TransactionItem', transactionItem);
  }

  /**
   * Adds a TransactionBatch, incorporating it into a matching TransactionItem. Will
   * create a new TransactionItem if none exists already.
   * @param {Realm}  database         The app wide local database
   * @param {object} transactionBatch The TransactionBatch to add to this Transaction
   */
  addBatchIfUnique(database, transactionBatch) {
    addBatchToParent(transactionBatch, this, () =>
      createRecord(database, 'TransactionItem', this, transactionBatch.itemBatch.item)
    );
  }

  /**
   * Delete any batches and items that aren't contributing to this transaction, in order to
   * remove clutter
   * @param  {Realm} database   App wide local database
   * @return {none}
   */
  pruneRedundantBatchesAndItems(database) {
    const batchesToRemove = this.getTransactionBatches(database)
                              .filtered('numberOfPacks = 0');

    database.delete('TransactionBatch', batchesToRemove);
    this.pruneBatchlessTransactionItems(database);
  }

  /**
   * Delete any items with no transactionBatches
   * @param  {Realm} database   App wide local database
   * @return {none}
   */
  pruneBatchlessTransactionItems(database) {
    const itemsToRemove = [];
    this.items.forEach(transactionItem => {
      if (transactionItem.batches.length === 0) {
        itemsToRemove.push(transactionItem);
      }
    });
    database.delete('TransactionItem', itemsToRemove);
  }

  /**
   * Returns all transaction batches for this transaction, return collection
   * is a realm collection so can be filtered
   * @param  {Realm} database   App wide local database
   * @return {RealmCollection} all transaction batches
   */
  getTransactionBatches(database) {
    return database
      .objects('TransactionBatch')
      .filtered('transaction.id == $0', this.id);
  }

  /**
   * Confirm this transaction, generating the associated item batches, linking them
   * to their items, and setting the status to confirmed.
   * @param  {Realm}  database The app wide local database
   * @return {none}
   */
  confirm(database) {
    if (this.isConfirmed) throw new Error('Cannot confirm as transaction is already confirmed');
    if (this.isFinalised) throw new Error('Cannot confirm as transaction is already finalised');
    const isIncomingInvoice = this.isIncoming;

    this.getTransactionBatches(database).forEach(transactionBatch => {
      const { itemBatch,
              batch,
              packSize,
              numberOfPacks,
              expiryDate,
              costPrice,
              sellPrice,
             } = transactionBatch;

      // Pack to one all transactions in mobile, so multiply by packSize to get quantity and price
      const packedToOneQuantity = numberOfPacks * packSize;
      const packedToOneCostPrice = costPrice / packSize;
      const packedToOneSellPrice = sellPrice / packSize;

      const newNumberOfPacks = isIncomingInvoice
          ? itemBatch.numberOfPacks + packedToOneQuantity
          : itemBatch.numberOfPacks - packedToOneQuantity;
      itemBatch.packSize = 1;
      itemBatch.numberOfPacks = newNumberOfPacks;
      itemBatch.expiryDate = expiryDate;
      itemBatch.batch = this.adjustBatchName(batch);
      itemBatch.costPrice = packedToOneCostPrice;
      itemBatch.sellPrice = packedToOneSellPrice;
      database.save('ItemBatch', itemBatch);
    });

    this.confirmDate = new Date();
    this.status = 'confirmed';
  }

  /**
   * Return 'supplierinvoice_${invoiceNumber}' of batch name is empty and
   * transaction is supplier invoice
   * @param  {string}  batchName
   * @return {string} adjustedBatchName
   */
  adjustBatchName(batchName) {
    if (this.isSupplierInvoice && (!batchName || batchName === '')) {
      return `supplierinvoice_${this.serialNumber}`;
    }
    return batchName;
  }

  /**
   * Finalise this transaction, setting the status so that this transaction is
   * locked down. If it has not already been confirmed (i.e. adjustments to inventory
   * made), confirm it first
   * @param  {Realm}  database The app wide local database
   * @return {none}
   */
  finalise(database) {
    if (this.isFinalised) throw new Error('Cannot finalise as transaction is already finalised');
    if (!this.isSupplierInvoice) this.pruneRedundantBatchesAndItems(database);
    if (!this.isConfirmed) this.confirm(database);
    this.status = 'finalised';
    database.save('Transaction', this);
  }
}

Transaction.schema = {
  name: 'Transaction',
  primaryKey: 'id',
  properties: {
    id: 'string',
    serialNumber: { type: 'string', default: 'placeholderSerialNumber' },
    otherParty: { type: 'Name', optional: true },
    comment: { type: 'string', optional: true },
    entryDate: { type: 'date', default: new Date() },
    type: { type: 'string', default: 'placeholderType' },
    status: { type: 'string', default: 'new' },
    confirmDate: { type: 'date', optional: true },
    enteredBy: { type: 'User', optional: true },
    theirRef: { type: 'string', optional: true }, // An external reference code
    category: { type: 'TransactionCategory', optional: true },
    items: { type: 'list', objectType: 'TransactionItem' },
    linkedRequisition: { type: 'Requisition', optional: true },
  },
};
<commit_msg>Prettier on Transaction.js<commit_after>import Realm from 'realm';
import {
  addBatchToParent,
  createRecord,
  getTotal,
  reuseNumber as reuseSerialNumber,
} from '../utilities';
import { SERIAL_NUMBER_KEYS } from '../index';
import { complement } from 'set-manipulator';

export class Transaction extends Realm.Object {
  constructor() {
    super();
    this.addItemsFromMasterList = this.addItemsFromMasterList.bind(this);
  }

  destructor(database) {
    if (this.isFinalised) throw new Error('Cannot delete finalised transaction');
    if (this.isCustomerInvoice) {
      reuseSerialNumber(database, SERIAL_NUMBER_KEYS.CUSTOMER_INVOICE, this.serialNumber);
    }
    database.delete('TransactionItem', this.items);
  }
  // Is external supplier invoice
  get isExternalSupplierInvoice() {
    return this.isSupplierInvoice && this.otherParty && this.otherParty.isExternalSupplier;
  }

  get isFinalised() {
    return this.status === 'finalised';
  }

  get isConfirmed() {
    return this.status === 'confirmed';
  }

  get isIncoming() {
    return this.type === 'supplier_invoice';
  }

  get isOutgoing() {
    return this.type === 'customer_invoice' || this.type === 'supplier_credit';
  }

  get isCustomerInvoice() {
    return this.type === 'customer_invoice';
  }

  get isSupplierInvoice() {
    return this.type === 'supplier_invoice';
  }

  get isInventoryAdjustment() {
    return this.otherParty && this.otherParty.type === 'inventory_adjustment';
  }

  get otherPartyName() {
    return this.otherParty ? this.otherParty.name : '';
  }

  get totalPrice() {
    return getTotal(this.items, 'totalPrice');
  }

  get totalQuantity() {
    return getTotal(this.items, 'totalQuantity');
  }

  get numberOfBatches() {
    return getTotal(this.items, 'numberOfBatches');
  }

  get numberOfItems() {
    return this.items.length;
  }

  hasItemWithId(itemId) {
    return this.items.filtered('item.id == $0', itemId).length > 0;
  }

  /**
   * Add a TransactionItem to this transaction, based on the given item. If it already
   * exists, do nothing.
   * @param {object} transactionItem  The TransactionItem to add
   */
  addItem(transactionItem) {
    this.items.push(transactionItem);
  }

  /**
   * Add all items from the customer's master list to this customer invoice
   */
  addItemsFromMasterList(database) {
    if (!this.isCustomerInvoice) throw new Error(`Cannot add master lists to ${this.type}`);
    if (this.isFinalised) throw new Error('Cannot add items to a finalised transaction');
    if (this.otherParty) {
      this.otherParty.masterLists.forEach(masterList => {
        const itemsToAdd = complement(masterList.items, this.items, item => item.itemId);
        itemsToAdd.forEach(masterListItem =>
          createRecord(database, 'TransactionItem', this, masterListItem.item),
        );
      });
    }
  }

  get isLinkedToRequisition() {
    return !!this.linkedRequisition;
  }

  /**
   * Remove the transaction items with the given ids from this transaction
   * @param  {Realm}  database        App wide local database
   * @param  {array}  itemIds         The ids of transactionItems to remove
   * @return {none}
   */
  removeItemsById(database, itemIds) {
    const itemsToDelete = [];
    for (let i = 0; i < itemIds.length; i++) {
      const transactionItem = this.items.find(testItem => testItem.id === itemIds[i]);
      if (transactionItem.isValid()) {
        itemsToDelete.push(transactionItem);
      }
    }
    database.delete('transactionItem', itemsToDelete);
  }

  /**
   * Remove transaction batches with given ids from this transaction, then prune any
   * TransactionItems that are emptied entirely of batches
   * @param  {Realm}  database  App wide local database
   * @param  {array}  transactionBatchIds The ids of transactionBatches to remove
   * @return {none}
   */
  removeTransactionBatchesById(database, transactionBatchIds) {
    if (this.isFinalised) throw new Error('Cannot modify finalised transaction');
    const transactionBatches = this.getTransactionBatches(database);
    const transactionBatchesToDelete = [];
    transactionBatchIds.forEach(transactionBatchId => {
      const transactionBatch = transactionBatches.find(
        matchTransactionBatch => matchTransactionBatch.id === transactionBatchId,
      );
      transactionBatchesToDelete.push(transactionBatch);
    });
    database.delete('TransactionBatch', transactionBatchesToDelete);
    this.pruneBatchlessTransactionItems(database);
  }

  /**
   * Remove the transaction item passed as param
   * @param  {Realm}  database        App wide local database
   * @param  {TransactionItem}  TransactionItem to remove
   * @return {none}
   */
  removeTransactionItem(database, transactionItem) {
    database.delete('TransactionItem', transactionItem);
  }

  /**
   * Adds a TransactionBatch, incorporating it into a matching TransactionItem. Will
   * create a new TransactionItem if none exists already.
   * @param {Realm}  database         The app wide local database
   * @param {object} transactionBatch The TransactionBatch to add to this Transaction
   */
  addBatchIfUnique(database, transactionBatch) {
    addBatchToParent(transactionBatch, this, () =>
      createRecord(database, 'TransactionItem', this, transactionBatch.itemBatch.item),
    );
  }

  /**
   * Delete any batches and items that aren't contributing to this transaction, in order to
   * remove clutter
   * @param  {Realm} database   App wide local database
   * @return {none}
   */
  pruneRedundantBatchesAndItems(database) {
    const batchesToRemove = this.getTransactionBatches(database).filtered('numberOfPacks = 0');

    database.delete('TransactionBatch', batchesToRemove);
    this.pruneBatchlessTransactionItems(database);
  }

  /**
   * Delete any items with no transactionBatches
   * @param  {Realm} database   App wide local database
   * @return {none}
   */
  pruneBatchlessTransactionItems(database) {
    const itemsToRemove = [];
    this.items.forEach(transactionItem => {
      if (transactionItem.batches.length === 0) {
        itemsToRemove.push(transactionItem);
      }
    });
    database.delete('TransactionItem', itemsToRemove);
  }

  /**
   * Returns all transaction batches for this transaction, return collection
   * is a realm collection so can be filtered
   * @param  {Realm} database   App wide local database
   * @return {RealmCollection} all transaction batches
   */
  getTransactionBatches(database) {
    return database.objects('TransactionBatch').filtered('transaction.id == $0', this.id);
  }

  /**
   * Confirm this transaction, generating the associated item batches, linking them
   * to their items, and setting the status to confirmed.
   * @param  {Realm}  database The app wide local database
   * @return {none}
   */
  confirm(database) {
    if (this.isConfirmed) throw new Error('Cannot confirm as transaction is already confirmed');
    if (this.isFinalised) throw new Error('Cannot confirm as transaction is already finalised');
    const isIncomingInvoice = this.isIncoming;

    this.getTransactionBatches(database).forEach(transactionBatch => {
      const {
        itemBatch,
        batch,
        packSize,
        numberOfPacks,
        expiryDate,
        costPrice,
        sellPrice,
      } = transactionBatch;

      // Pack to one all transactions in mobile, so multiply by packSize to get quantity and price
      const packedToOneQuantity = numberOfPacks * packSize;
      const packedToOneCostPrice = costPrice / packSize;
      const packedToOneSellPrice = sellPrice / packSize;

      const newNumberOfPacks = isIncomingInvoice
        ? itemBatch.numberOfPacks + packedToOneQuantity
        : itemBatch.numberOfPacks - packedToOneQuantity;
      itemBatch.packSize = 1;
      itemBatch.numberOfPacks = newNumberOfPacks;
      itemBatch.expiryDate = expiryDate;
      itemBatch.batch = this.adjustBatchName(batch);
      itemBatch.costPrice = packedToOneCostPrice;
      itemBatch.sellPrice = packedToOneSellPrice;
      database.save('ItemBatch', itemBatch);
    });

    this.confirmDate = new Date();
    this.status = 'confirmed';
  }

  /**
   * Return 'supplierinvoice_${invoiceNumber}' of batch name is empty and
   * transaction is supplier invoice
   * @param  {string}  batchName
   * @return {string} adjustedBatchName
   */
  adjustBatchName(batchName) {
    if (this.isSupplierInvoice && (!batchName || batchName === '')) {
      return `supplierinvoice_${this.serialNumber}`;
    }
    return batchName;
  }

  /**
   * Finalise this transaction, setting the status so that this transaction is
   * locked down. If it has not already been confirmed (i.e. adjustments to inventory
   * made), confirm it first
   * @param  {Realm}  database The app wide local database
   * @return {none}
   */
  finalise(database) {
    if (this.isFinalised) throw new Error('Cannot finalise as transaction is already finalised');
    if (!this.isSupplierInvoice) this.pruneRedundantBatchesAndItems(database);
    if (!this.isConfirmed) this.confirm(database);
    this.status = 'finalised';
    database.save('Transaction', this);
  }
}

Transaction.schema = {
  name: 'Transaction',
  primaryKey: 'id',
  properties: {
    id: 'string',
    serialNumber: { type: 'string', default: 'placeholderSerialNumber' },
    otherParty: { type: 'Name', optional: true },
    comment: { type: 'string', optional: true },
    entryDate: { type: 'date', default: new Date() },
    type: { type: 'string', default: 'placeholderType' },
    status: { type: 'string', default: 'new' },
    confirmDate: { type: 'date', optional: true },
    enteredBy: { type: 'User', optional: true },
    theirRef: { type: 'string', optional: true }, // An external reference code
    category: { type: 'TransactionCategory', optional: true },
    items: { type: 'list', objectType: 'TransactionItem' },
    linkedRequisition: { type: 'Requisition', optional: true },
  },
};
<|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: {
        classType: '=dimClass',
        store: '=dimLoadoutPopup'
      },
      replace: true,
      template: [
        '
',
        '

+ Create Loadout

',
        '
',
        '
',
        '      {{ loadout.name }}',
        '      ',
        '      ',
        '
',
        '
',
        '

',
        '     Maximize Light',
        '

',
        '
'
      ].join('')
    };
  }

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

  function LoadoutPopupCtrl($rootScope, ngDialog, dimLoadoutService, dimItemService, toaster, $q, dimStoreService, dimItemTier) {
    var vm = this;

    vm.classTypeId = -1;

    var chooseClass = {
      'warlock': 0,
      'titan': 1,
      'hunter': 2
    };

    vm.classTypeId = chooseClass[vm.classType] || 0;

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

        vm.loadouts = _.filter(vm.loadouts, function(item) {
          return ((item.classType === -1) || (item.classType === vm.classTypeId));
        });
      });

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

    vm.deleteLoadout = function deleteLoadout(loadout, $event) {
      dimLoadoutService.deleteLoadout(loadout);
      $rootScope.$broadcast('dim-delete-loadout', {});

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

          vm.loadouts = _.filter(vm.loadouts, function(item) {
            return ((item.classType === -1) || (item.classType === vm.classTypeId));
          });
        });
    };

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

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

      var scope = {
        failed: false
      };

      var items = _.chain(loadout.items)
        .values()
        .flatten()
        .value();

      var _types = _.chain(items)
        .pluck('type')
        .uniq()
        .value();

      var _items = _.chain(vm.store.items)
        .filter(function(item) {
          return _.contains(_types, item.type);
        })
        .filter(function(item) {
          return (!_.some(items, function(i) {
            return ((i.id === item.id) && (i.hash === item.hash));
          }));
        })
        .groupBy(function(item) {
          return item.type;
        })
        .value();

      applyLoadoutItems(items, loadout, _items, scope);
    };

    // 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'];

      // TODO: this should be a method somewhere that gets all items equippable by a character
      var applicableItems = _.select(dimItemService.getItems(), function(i) {
        return i.equipment &&
          i.primStat !== undefined && // has a primary stat (sanity check)
          (i.classTypeName === 'unknown' || i.classTypeName === vm.classType) && // for our class
          i.equipRequiredLevel <= vm.store.level && // nothing we are too low-level to equip
          _.contains(lightTypes, i.type) && // one of our selected types
          !i.notransfer; // can be moved
      });
      var itemsByType = _.groupBy(applicableItems, 'type');

      var bestItemFn = function(item) {
        // Add .1 to the value if equipped, to break ties and avoid changing currently-equipped items out
        // for other stuff with the same level.
        // Add another .1 to prefer breaking ties with items already on this character.
        return item.primStat.value + (item.equipped ? 0.1 : 0) + (item.owner == vm.store.id ? 0.1 : 0);
      };
      var isExotic = function(item) {
        return item.tier === dimItemTier.exotic;
      };

      // Pick the best item by primary stat
      var items = {};
      _.each(lightTypes, function(type) {
        if (itemsByType.hasOwnProperty(type)) {
          items[type] = _.max(itemsByType[type], 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 primary stats add up to the biggest number, again favoring equipped stuff
          var bestOption = _.max(options, function(opt) { return _.reduce(_.values(opt), function(sum, i) { return bestItemFn(i) + sum; }, 0) });
          _.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 ];
      });

      var loadout = {
        classType: -1,
        name: 'Maximize Light',
        items: finalItems
      };

      vm.applyLoadout(loadout, $event);
    };

    function applyLoadoutItems(items, loadout, _items, scope) {
      if (items.length > 0) {
        var pseudoItem = items.splice(0, 1)[0];
        var item = dimItemService.getItem(pseudoItem);

        if (item.type === 'Class') {
          item = _.findWhere(vm.store.items, {
            hash: pseudoItem.hash
          });
        }

        if (item) {
          var size = _.chain(vm.store.items)
            .filter(function(i) {
              return item.type === i.type;
            })
            .size()
            .value();

          var p = $q.when(item);

          var target;

          if (size === 10) {
            if (item.owner !== vm.store.id) {
              var moveItem = _items[item.type].splice(0, 1);
              p = $q.when(dimStoreService.getStores())
                .then(function(stores) {
                  return _.chain(stores)
                    // .filter(function(s) {
                    //   return (s.id !== 'vault');
                    // })
                    .sortBy(function(s) {
                      if (s.id === vm.store.id) {
                        return 0;
                      } else if (s.id === 'vault') {
                        return 2;
                      } else {
                        return 1;
                      }
                    })
                    .value();
                })
                .then(function(sortedStores) {
                  return _.find(sortedStores, function(s) {
                    return _.chain(s.items)
                      .filter(function(i) {
                        return item.type === i.type;
                      })
                      .size()
                      .value() < ((s.id === 'vault') ? 72 : 10);
                  });
                })
                .then(function(t) {
                  if (_.isUndefined(t)) {
                    throw new Error('Collector eh?  All your characters ' + moveItem[0].type.toLowerCase() + ' slots are full and I can\'t move items of characters, yet... Clear a slot on a character and I can complete the loadout.');
                  }
                  target = t;

                  return dimItemService.moveTo(moveItem[0], target);
                });
            }
          }

          var promise = p
            .then(function() {
              return dimItemService.moveTo(item, vm.store, pseudoItem.equipped);
            })
            .catch(function(a) {
              scope.failed = true;
              toaster.pop('error', item.name, a.message);
            })
            .finally(function() {
              applyLoadoutItems(items, loadout, _items, scope);
            });

          $rootScope.loadingTracker.addPromise(promise);
        }
      } else {
        var dimStores;

        $q.when(dimStoreService.getStores())
          .then(function(stores) {
            dimStores = stores;
            return dimStoreService.updateStores();
          })
          .then(function(bungieStores) {
            _.each(dimStores, function(dStore) {
              if (dStore.id !== 'vault') {
                var bStore = _.find(bungieStores, function(bStore) {
                  return dStore.id === bStore.id;
                });

                dStore.level = bStore.base.characterLevel;
                dStore.percentToNextLevel = bStore.base.percentToNextLevel;
                dStore.powerLevel = bStore.base.characterBase.powerLevel;
                dStore.background = bStore.base.backgroundPath;
                dStore.icon = bStore.base.emblemPath;
              }
            })
          })
          .then(function() {
            var value = 'success';
            var message = 'Your loadout has been transfered.';

            if (scope.failed) {
              value = 'warning';
              message = 'Your loadout has been transfered, with errors.'
            }

            toaster.pop(value, loadout.name, message);
          });
      }
    }
  }
})();
<commit_msg>Boost value of an item to the max light loadout only if it's equipped on the same character<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: {
        classType: '=dimClass',
        store: '=dimLoadoutPopup'
      },
      replace: true,
      template: [
        '
',
        '

+ Create Loadout

',
        '
',
        '
',
        '      {{ loadout.name }}',
        '      ',
        '      ',
        '
',
        '
',
        '

',
        '     Maximize Light',
        '

',
        '
'
      ].join('')
    };
  }

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

  function LoadoutPopupCtrl($rootScope, ngDialog, dimLoadoutService, dimItemService, toaster, $q, dimStoreService, dimItemTier) {
    var vm = this;

    vm.classTypeId = -1;

    var chooseClass = {
      'warlock': 0,
      'titan': 1,
      'hunter': 2
    };

    vm.classTypeId = chooseClass[vm.classType] || 0;

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

        vm.loadouts = _.filter(vm.loadouts, function(item) {
          return ((item.classType === -1) || (item.classType === vm.classTypeId));
        });
      });

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

    vm.deleteLoadout = function deleteLoadout(loadout, $event) {
      dimLoadoutService.deleteLoadout(loadout);
      $rootScope.$broadcast('dim-delete-loadout', {});

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

          vm.loadouts = _.filter(vm.loadouts, function(item) {
            return ((item.classType === -1) || (item.classType === vm.classTypeId));
          });
        });
    };

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

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

      var scope = {
        failed: false
      };

      var items = _.chain(loadout.items)
        .values()
        .flatten()
        .value();

      var _types = _.chain(items)
        .pluck('type')
        .uniq()
        .value();

      var _items = _.chain(vm.store.items)
        .filter(function(item) {
          return _.contains(_types, item.type);
        })
        .filter(function(item) {
          return (!_.some(items, function(i) {
            return ((i.id === item.id) && (i.hash === item.hash));
          }));
        })
        .groupBy(function(item) {
          return item.type;
        })
        .value();

      applyLoadoutItems(items, loadout, _items, scope);
    };

    // 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'];

      // TODO: this should be a method somewhere that gets all items equippable by a character
      var applicableItems = _.select(dimItemService.getItems(), function(i) {
        return i.equipment &&
          i.primStat !== undefined && // has a primary stat (sanity check)
          (i.classTypeName === 'unknown' || i.classTypeName === vm.classType) && // for our class
          i.equipRequiredLevel <= vm.store.level && // nothing we are too low-level to equip
          _.contains(lightTypes, i.type) && // one of our selected types
          !i.notransfer; // can be moved
      });
      var itemsByType = _.groupBy(applicableItems, 'type');

      var bestItemFn = function(item) {
        // Add .1 to the value if equipped, to break ties and avoid changing currently-equipped items out
        // for other stuff with the same level.
        // Add another .1 to prefer breaking ties with items already on this character.
        return item.primStat.value + (item.owner == vm.store.id ? (item.equipped ? 0.2 : 0.1) : 0);
      };
      var isExotic = function(item) {
        return item.tier === dimItemTier.exotic;
      };

      // Pick the best item by primary stat
      var items = {};
      _.each(lightTypes, function(type) {
        if (itemsByType.hasOwnProperty(type)) {
          items[type] = _.max(itemsByType[type], 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 primary stats add up to the biggest number, again favoring equipped stuff
          var bestOption = _.max(options, function(opt) { return _.reduce(_.values(opt), function(sum, i) { return bestItemFn(i) + sum; }, 0) });
          _.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 ];
      });

      var loadout = {
        classType: -1,
        name: 'Maximize Light',
        items: finalItems
      };

      vm.applyLoadout(loadout, $event);
    };

    function applyLoadoutItems(items, loadout, _items, scope) {
      if (items.length > 0) {
        var pseudoItem = items.splice(0, 1)[0];
        var item = dimItemService.getItem(pseudoItem);

        if (item.type === 'Class') {
          item = _.findWhere(vm.store.items, {
            hash: pseudoItem.hash
          });
        }

        if (item) {
          var size = _.chain(vm.store.items)
            .filter(function(i) {
              return item.type === i.type;
            })
            .size()
            .value();

          var p = $q.when(item);

          var target;

          if (size === 10) {
            if (item.owner !== vm.store.id) {
              var moveItem = _items[item.type].splice(0, 1);
              p = $q.when(dimStoreService.getStores())
                .then(function(stores) {
                  return _.chain(stores)
                    // .filter(function(s) {
                    //   return (s.id !== 'vault');
                    // })
                    .sortBy(function(s) {
                      if (s.id === vm.store.id) {
                        return 0;
                      } else if (s.id === 'vault') {
                        return 2;
                      } else {
                        return 1;
                      }
                    })
                    .value();
                })
                .then(function(sortedStores) {
                  return _.find(sortedStores, function(s) {
                    return _.chain(s.items)
                      .filter(function(i) {
                        return item.type === i.type;
                      })
                      .size()
                      .value() < ((s.id === 'vault') ? 72 : 10);
                  });
                })
                .then(function(t) {
                  if (_.isUndefined(t)) {
                    throw new Error('Collector eh?  All your characters ' + moveItem[0].type.toLowerCase() + ' slots are full and I can\'t move items of characters, yet... Clear a slot on a character and I can complete the loadout.');
                  }
                  target = t;

                  return dimItemService.moveTo(moveItem[0], target);
                });
            }
          }

          var promise = p
            .then(function() {
              return dimItemService.moveTo(item, vm.store, pseudoItem.equipped);
            })
            .catch(function(a) {
              scope.failed = true;
              toaster.pop('error', item.name, a.message);
            })
            .finally(function() {
              applyLoadoutItems(items, loadout, _items, scope);
            });

          $rootScope.loadingTracker.addPromise(promise);
        }
      } else {
        var dimStores;

        $q.when(dimStoreService.getStores())
          .then(function(stores) {
            dimStores = stores;
            return dimStoreService.updateStores();
          })
          .then(function(bungieStores) {
            _.each(dimStores, function(dStore) {
              if (dStore.id !== 'vault') {
                var bStore = _.find(bungieStores, function(bStore) {
                  return dStore.id === bStore.id;
                });

                dStore.level = bStore.base.characterLevel;
                dStore.percentToNextLevel = bStore.base.percentToNextLevel;
                dStore.powerLevel = bStore.base.characterBase.powerLevel;
                dStore.background = bStore.base.backgroundPath;
                dStore.icon = bStore.base.emblemPath;
              }
            })
          })
          .then(function() {
            var value = 'success';
            var message = 'Your loadout has been transfered.';

            if (scope.failed) {
              value = 'warning';
              message = 'Your loadout has been transfered, with errors.'
            }

            toaster.pop(value, loadout.name, message);
          });
      }
    }
  }
})();
<|endoftext|>


<commit_before>module("Network Tests");
var asNEAT = require('asNEAT/asNEAT')['default'],
    Connection = require('asNEAT/connection')['default'],
    CompressorNode = require('asNEAT/nodes/compressorNode')['default'],
    ConvolverNode = require('asNEAT/nodes/convolverNode')['default'],
    DelayNode = require('asNEAT/nodes/delayNode')['default'],
    FeedbackDelayNode = require('asNEAT/nodes/feedbackDelayNode')['default'],
    FilterNode = require('asNEAT/nodes/filterNode')['default'],
    GainNode = require('asNEAT/nodes/gainNode')['default'],
    NoteOscillatorNode = require('asNEAT/nodes/noteOscillatorNode')['default'],
    OscillatorNode = require('asNEAT/nodes/oscillatorNode')['default'],
    Network = require('asNEAT/network')['default'];

test('network', function() {
  var a = new Network();
  equal(a.connections.length, 1, "network starts with single connection");
});

test("Same default objects/arrays not referenced by multiple networks", function() {
  var a = new Network();
  var b = new Network();
  notEqual(a.nodes, b.nodes, "Nodes not the same");
});

test("crossWith", function() {
  var a,b,c,
      generation = 3;

  a = new Network();
  c = a.crossWith(a);
  ok(a.nodes.length === c.nodes.length,
    "crossing with itself contains same number of nodes");
  ok(a.connections.length === c.connections.length,
    "crossing with itself contains same number of connections");

  a = new Network();
  b = new Network({generation: generation});
  c = a.crossWith(b);
  equal(c.nodes.length, a.nodes.length+b.nodes.length-1,
    "Child has same number nodes as a+b, less the shared output node");
  equal(c.connections.length, a.connections.length+b.connections.length,
    "Child has same number connections");
  equal(c.generation, generation, "Child copies largest generation number");

  a = new Network();
  b = a.clone();
  a.splitMutation();
  b.addOscillator();
  c = a.crossWith(b);
  ok(c.nodes.length === (a.nodes.length+1) &&
     c.nodes.length === (b.nodes.length+1),
    "SplitMutation && addOscillator adds correct number nodes");
  ok(c.connections.length === (a.connections.length+1) &&
     c.connections.length === (b.connections.length+2),
    "SplitMutation && addOscillator adds correct number connections");
  ok(_.reduce(c.nodes, function(result, val){
    return result && (
      _.some(a.nodes, {'id': val.id}) ||
      _.some(b.nodes, {'id': val.id})
    );
  }, true), "C contains only nodes found in a and b");
  ok(_.reduce(c.connections, function(result, val){
    return result && (
      _.some(a.connections, {'id': val.id}) ||
      _.some(b.connections, {'id': val.id})
    );
  }, true), "C contains only connections found in a and b");

  equal(c.nodes.length, _.uniq(c.nodes, true, 'id').length,
    "C contains no duplicate nodes");
  equal(c.connections.length, _.uniq(c.connections, true, 'id').length,
    "C contains no duplicate connections");
});

test("split node", function() {
  var a = new Network(),
      connection = a.connections[0];

  a.splitMutation();
  equal(a.connections.length, 3, "connections increased (1_old+2_new)");
  ok(!connection.enabled, "old connection is disabled");
});

test("getNoteOscillatorNodes", function() {
  var a = new Network(),
      firstOscillator = a.nodes[1];

  equal(firstOscillator, a.getNoteOscillatorNodes()[0], "gets first oscillator");
  equal(a.getNoteOscillatorNodes().length, 1, "starts with one");

  a.addOscillator();
  equal(a.getNoteOscillatorNodes().length +a.getOscillatorNodes().length, 2,
    "addOscillator increases to 2");
  equal(a.getNoteOscillatorNodes().length +a.getOscillatorNodes().length,
    a.getOscillatorAndNoteOscillatorNodes().length,
    "getNoteOsc + getOsc === getOscAndNoteOsc");
});

test('mutate', function() {
  var a = new Network();
  a.mutate({
    splitMutationChance: 0.0,
    addOscillatorChance: 0.0,
    addConnectionChance: 0.0,
    mutateConnectionWeightsChance: 1.0,
    mutateNodeParametersChance: 0.0,
    mutationDistance: 0.1
  });
  ok(a.lastMutation.changeDescription === "Mutated connection gain", "Forces connection weight mutation");
});

test("mutations update lastMutation", function() {
  var a = new Network();
  equal(a.lastMutation, null, "starts null");
  testLastMutation(a.clone().splitMutation(), "splitMutation");
  testLastMutation(a.clone().addOscillator(), "addOscillator");
  testLastMutation(a.clone().addConnection(), "addConnection");
  testLastMutation(a.clone().mutateConnectionWeights(), "mutateConnectionWeights");
  testLastMutation(a.clone().mutateNodeParameters(), "mutateNodeParameters");

  function testLastMutation(net, msg) {
    ok(_.isArray(net.lastMutation.objectsChanged) &&
       _.isString(net.lastMutation.changeDescription), msg);
  }
});

test("createFromJSON", function() {
  var a = new Network();
  a.mutate().mutate().mutate().mutate();
  a.splitMutation();
  a.addOscillator();
  a.addConnection();
  var json = a.toJSON();
  var b = Network.createFromJSON(json);
  equal(a.nodes.length, b.nodes.length, "Preserves number of nodes");
  equal(a.connections.length, b.connections.length, "Preserves number of connections");

  var inOriginal = true;
  _.forEach(b.nodes, function(node) {
      inOriginal = inOriginal && _.find(a.nodes, {id:node.id});
  });
  ok(inOriginal, "every node found in original");

  inOriginal = true;
  _.forEach(b.connections, function(connection) {
      inOriginal = inOriginal && _.find(a.connections, {id:connection.id});
  });
  ok(inOriginal, "every connection found in original");

  // Manually test each node type
  var osc = NoteOscillatorNode.random();
  osc.noteOffset = 0;

  var compressorNode = CompressorNode.random(),
      convolverNode = ConvolverNode.random(),
      delayNode = DelayNode.random(),
      feedbackDelayNode = FeedbackDelayNode.random(),
      filterNode = FilterNode.random(),
      gainNode = GainNode.random(),
      oscillatorNode = OscillatorNode.random(),
      osc2 = NoteOscillatorNode.random();
  osc2.noteOffset = 2;
  var nodes = [
    asNEAT.globalOutNode,
    osc,
    compressorNode,
    convolverNode,
    delayNode,
    feedbackDelayNode,
    filterNode,
    gainNode,
    oscillatorNode,
    osc2
  ];
  var connections = [
    new Connection({ sourceNode: nodes[1], targetNode: nodes[0], weight: 0.5, enabled: false}),
    new Connection({ sourceNode: nodes[1], targetNode: nodes[2], weight: 0.1}),
    new Connection({ sourceNode: nodes[2], targetNode: nodes[3], weight: 1.0}),
    new Connection({ sourceNode: nodes[4], targetNode: nodes[5], weight: 1.0}),
    new Connection({ sourceNode: nodes[5], targetNode: nodes[6], weight: 1.0}),
    new Connection({ sourceNode: nodes[6], targetNode: nodes[7], weight: 1.0}),
    new Connection({ sourceNode: nodes[7], targetNode: nodes[0], weight: 1.0}),
    new Connection({ sourceNode: nodes[8], targetNode: nodes[1], weight: 1.0,
      targetParameter: 'frequency', targetParameterNodeName: 'oscNode'}),
    new Connection({ sourceNode: nodes[9], targetNode: nodes[0], weight: 1.0}),
  ];
  a = new Network({
    nodes: nodes,
    connections: connections
  });
  a.nodes[0].hasChanged = false;
  b = Network.createFromJSON(a.toJSON());
  // Compare nodes
  (function compare(a, b, i) {
    if (i===0) {
      return;
    }
    _.forEach(a, function(value, key) {
      if (typeof value === 'object') {
        compare(a[key], b[key], i-1);
        return;
      }
      equal(a[key], b[key], 'matching '+key+' = '+value);
    });
  })(a,b,3);
});

test('evolutionHistory', function() {
  var a = new Network();
  equal(a.evolutionHistory.length, 0, 'Instrument starts with no history');
  a.splitMutation();
  equal(a.evolutionHistory.length, 1, 'History increases by one');
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'sm', 'splitMutation logs correctly');
  a.addOscillator();
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'ao', 'addOscillator logs correctly');
  a.addConnection();
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'ac', 'addConnection logs correctly');
  a.mutateConnectionWeights();
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'mcw', 'mutateConnectionWeights logs correctly');
  a.mutateNodeParameters();
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'mnp', 'mutateNodeParameters logs correctly');

  var b = new Network();
  equal(b.evolutionHistory.length, 0, 'New instruments don\'t copy old history');
  b.splitMutation();
  b.splitMutation();
  var c = a.crossWith(b);
  equal(c.evolutionHistory[c.evolutionHistory.length-1], 'co', 'crossover logs correctly');
  equal(c.evolutionHistory.length, 8, 'crossover combines history');

  equal(c.clone().evolutionHistory.length, 8, 'clone preserves history');

  equal(Network.createFromJSON(c.toJSON()).evolutionHistory.length, 8, 'JSON preserves history');
});
<commit_msg>extra JSON test<commit_after>module("Network Tests");
var asNEAT = require('asNEAT/asNEAT')['default'],
    Connection = require('asNEAT/connection')['default'],
    CompressorNode = require('asNEAT/nodes/compressorNode')['default'],
    ConvolverNode = require('asNEAT/nodes/convolverNode')['default'],
    DelayNode = require('asNEAT/nodes/delayNode')['default'],
    FeedbackDelayNode = require('asNEAT/nodes/feedbackDelayNode')['default'],
    FilterNode = require('asNEAT/nodes/filterNode')['default'],
    GainNode = require('asNEAT/nodes/gainNode')['default'],
    NoteOscillatorNode = require('asNEAT/nodes/noteOscillatorNode')['default'],
    OscillatorNode = require('asNEAT/nodes/oscillatorNode')['default'],
    Network = require('asNEAT/network')['default'];

test('network', function() {
  var a = new Network();
  equal(a.connections.length, 1, "network starts with single connection");
});

test("Same default objects/arrays not referenced by multiple networks", function() {
  var a = new Network();
  var b = new Network();
  notEqual(a.nodes, b.nodes, "Nodes not the same");
});

test("crossWith", function() {
  var a,b,c,
      generation = 3;

  a = new Network();
  c = a.crossWith(a);
  ok(a.nodes.length === c.nodes.length,
    "crossing with itself contains same number of nodes");
  ok(a.connections.length === c.connections.length,
    "crossing with itself contains same number of connections");

  a = new Network();
  b = new Network({generation: generation});
  c = a.crossWith(b);
  equal(c.nodes.length, a.nodes.length+b.nodes.length-1,
    "Child has same number nodes as a+b, less the shared output node");
  equal(c.connections.length, a.connections.length+b.connections.length,
    "Child has same number connections");
  equal(c.generation, generation, "Child copies largest generation number");

  a = new Network();
  b = a.clone();
  a.splitMutation();
  b.addOscillator();
  c = a.crossWith(b);
  ok(c.nodes.length === (a.nodes.length+1) &&
     c.nodes.length === (b.nodes.length+1),
    "SplitMutation && addOscillator adds correct number nodes");
  ok(c.connections.length === (a.connections.length+1) &&
     c.connections.length === (b.connections.length+2),
    "SplitMutation && addOscillator adds correct number connections");
  ok(_.reduce(c.nodes, function(result, val){
    return result && (
      _.some(a.nodes, {'id': val.id}) ||
      _.some(b.nodes, {'id': val.id})
    );
  }, true), "C contains only nodes found in a and b");
  ok(_.reduce(c.connections, function(result, val){
    return result && (
      _.some(a.connections, {'id': val.id}) ||
      _.some(b.connections, {'id': val.id})
    );
  }, true), "C contains only connections found in a and b");

  equal(c.nodes.length, _.uniq(c.nodes, true, 'id').length,
    "C contains no duplicate nodes");
  equal(c.connections.length, _.uniq(c.connections, true, 'id').length,
    "C contains no duplicate connections");
});

test("split node", function() {
  var a = new Network(),
      connection = a.connections[0];

  a.splitMutation();
  equal(a.connections.length, 3, "connections increased (1_old+2_new)");
  ok(!connection.enabled, "old connection is disabled");
});

test("getNoteOscillatorNodes", function() {
  var a = new Network(),
      firstOscillator = a.nodes[1];

  equal(firstOscillator, a.getNoteOscillatorNodes()[0], "gets first oscillator");
  equal(a.getNoteOscillatorNodes().length, 1, "starts with one");

  a.addOscillator();
  equal(a.getNoteOscillatorNodes().length +a.getOscillatorNodes().length, 2,
    "addOscillator increases to 2");
  equal(a.getNoteOscillatorNodes().length +a.getOscillatorNodes().length,
    a.getOscillatorAndNoteOscillatorNodes().length,
    "getNoteOsc + getOsc === getOscAndNoteOsc");
});

test('mutate', function() {
  var a = new Network();
  a.mutate({
    splitMutationChance: 0.0,
    addOscillatorChance: 0.0,
    addConnectionChance: 0.0,
    mutateConnectionWeightsChance: 1.0,
    mutateNodeParametersChance: 0.0,
    mutationDistance: 0.1
  });
  ok(a.lastMutation.changeDescription === "Mutated connection gain", "Forces connection weight mutation");
});

test("mutations update lastMutation", function() {
  var a = new Network();
  equal(a.lastMutation, null, "starts null");
  testLastMutation(a.clone().splitMutation(), "splitMutation");
  testLastMutation(a.clone().addOscillator(), "addOscillator");
  testLastMutation(a.clone().addConnection(), "addConnection");
  testLastMutation(a.clone().mutateConnectionWeights(), "mutateConnectionWeights");
  testLastMutation(a.clone().mutateNodeParameters(), "mutateNodeParameters");

  function testLastMutation(net, msg) {
    ok(_.isArray(net.lastMutation.objectsChanged) &&
       _.isString(net.lastMutation.changeDescription), msg);
  }
});

test("createFromJSON", function() {
  var a = new Network();
  a.mutate().mutate().mutate().mutate();
  a.splitMutation();
  a.addOscillator();
  a.addConnection();
  var json = a.toJSON();
  var b = Network.createFromJSON(json);
  equal(a.nodes.length, b.nodes.length, "Preserves number of nodes");
  equal(a.connections.length, b.connections.length, "Preserves number of connections");

  var inOriginal = true;
  _.forEach(b.nodes, function(node) {
      inOriginal = inOriginal && _.find(a.nodes, {id:node.id});
  });
  ok(inOriginal, "every node found in original");

  inOriginal = true;
  _.forEach(b.connections, function(connection) {
      inOriginal = inOriginal && _.find(a.connections, {id:connection.id});
  });
  ok(inOriginal, "every connection found in original");

  // Manually test each node type
  var osc = NoteOscillatorNode.random();
  osc.noteOffset = 0;

  var compressorNode = CompressorNode.random(),
      convolverNode = ConvolverNode.random(),
      delayNode = DelayNode.random(),
      feedbackDelayNode = FeedbackDelayNode.random(),
      filterNode = FilterNode.random(),
      gainNode = GainNode.random(),
      oscillatorNode = OscillatorNode.random(),
      osc2 = NoteOscillatorNode.random();
  osc2.noteOffset = 2;
  var nodes = [
    asNEAT.globalOutNode,
    osc,
    compressorNode,
    convolverNode,
    delayNode,
    feedbackDelayNode,
    filterNode,
    gainNode,
    oscillatorNode,
    osc2
  ];
  var connections = [
    new Connection({ sourceNode: nodes[1], targetNode: nodes[0], weight: 0.5, enabled: false}),
    new Connection({ sourceNode: nodes[1], targetNode: nodes[2], weight: 0.1}),
    new Connection({ sourceNode: nodes[2], targetNode: nodes[3], weight: 1.0}),
    new Connection({ sourceNode: nodes[4], targetNode: nodes[5], weight: 1.0}),
    new Connection({ sourceNode: nodes[5], targetNode: nodes[6], weight: 1.0}),
    new Connection({ sourceNode: nodes[6], targetNode: nodes[7], weight: 1.0}),
    new Connection({ sourceNode: nodes[7], targetNode: nodes[0], weight: 1.0}),
    new Connection({ sourceNode: nodes[8], targetNode: nodes[1], weight: 1.0,
      targetParameter: 'frequency', targetParameterNodeName: 'oscNode'}),
    new Connection({ sourceNode: nodes[9], targetNode: nodes[0], weight: 1.0}),
  ];
  a = new Network({
    nodes: nodes,
    connections: connections
  });
  a.nodes[0].hasChanged = false;
  b = Network.createFromJSON(a.toJSON());
  // Compare nodes
  (function compare(a, b, i) {
    if (i===0) {
      return;
    }
    _.forEach(a, function(value, key) {
      if (typeof value === 'object') {
        compare(a[key], b[key], i-1);
        return;
      }
      equal(a[key], b[key], 'matching '+key+' = '+value);
    });
  })(a,b,3);
  equal(a.toJSON(), b.toJSON(), 'from JSON creates same JSON as original');
});

test('evolutionHistory', function() {
  var a = new Network();
  equal(a.evolutionHistory.length, 0, 'Instrument starts with no history');
  a.splitMutation();
  equal(a.evolutionHistory.length, 1, 'History increases by one');
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'sm', 'splitMutation logs correctly');
  a.addOscillator();
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'ao', 'addOscillator logs correctly');
  a.addConnection();
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'ac', 'addConnection logs correctly');
  a.mutateConnectionWeights();
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'mcw', 'mutateConnectionWeights logs correctly');
  a.mutateNodeParameters();
  equal(a.evolutionHistory[a.evolutionHistory.length-1], 'mnp', 'mutateNodeParameters logs correctly');

  var b = new Network();
  equal(b.evolutionHistory.length, 0, 'New instruments don\'t copy old history');
  b.splitMutation();
  b.splitMutation();
  var c = a.crossWith(b);
  equal(c.evolutionHistory[c.evolutionHistory.length-1], 'co', 'crossover logs correctly');
  equal(c.evolutionHistory.length, 8, 'crossover combines history');

  equal(c.clone().evolutionHistory.length, 8, 'clone preserves history');

  equal(Network.createFromJSON(c.toJSON()).evolutionHistory.length, 8, 'JSON preserves history');
});
<|endoftext|>


<commit_before>(function ($) {
    if (typeof dnn == 'undefined') window.dnn = {};
    if (typeof dnn.searchSkinObject == 'undefined') {
        dnn.searchSkinObject = function (options) {
            var settings = {
                delayTriggerAutoSearch: 100,
                minCharRequiredTriggerAutoSearch: 2,
                searchType: 'S',
                enableWildSearch: true,
                cultureCode: 'en-US'
            };
            this.settings = $.extend({}, settings, options);
        };
        dnn.searchSkinObject.prototype = {
            _ignoreKeyCodes: [9, 13, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45],
            init: function () {
                var throttle = null, self = this;
                var makeUrl = function (val, service) {
                    var url = service ? service.getServiceRoot('internalservices') + 'searchService/preview' : null;
                    if (!url) return null;
                    var params = {};
                    params['keywords'] = val.replace(/^\s+|\s+$/g, '');
                    if (!self.settings.enableWildSearch) params["forceWild"] = "0";
                    params['culture'] = self.settings.cultureCode;
                    if (self.settings.portalId >= 0)
                        params['portal'] = self.settings.portalId;
                    var urlAppend = [];
                    $.each(params, function (index, value) {
                        urlAppend.push([index, encodeURIComponent(value)].join('='));
                    });

                    if (urlAppend.length) {
                        url += url.indexOf('?') === -1 ? '?' : '&';
                        url += urlAppend.join('&');
                    }
                    return url;
                };

                var generatePreviewTemplate = function (data, $wrap) {
                    var preview = $('.searchSkinObjectPreview', $wrap);
                    if (preview.length)
                        preview.remove();

                    var markup = '
';
                    if (data && data.length) {
                        for (var i = 0; i < data.length; i++) {
                            var group = data[i];
                            if (group.Results && group.Results.length) {
                                var groupTitle = group.DocumentTypeName;
                                markup += '
' + groupTitle + '
';
                                for (var j = 0; j < group.Results.length; j++) {
                                    var item = group.Results[j];
                                    var itemTitle = item.Title;
                                    var itemUrl = item.DocumentUrl;
                                    var itemDescription = item.Description;
                                    var itemSnippet = item.Snippet;
                                    markup += '
';
                                    if (item.Attributes.Avatar) {
                                        markup += '';
                                    }
                                    markup += '' + itemTitle + '';
                                    if (itemDescription) {
                                        markup += '
' + itemDescription + '
';
                                    }
                                    if (itemSnippet) {
                                        markup += '
' + itemSnippet + '
';
                                    }
                                    markup += '
';
                                } // end for group items
                            }
                        } // end for group

                        var moreResults = $wrap.attr('data-moreresults');
                        markup += '
' + moreResults + '
';
                        markup += '
';
                    }
                    else {
                        var noResult = $wrap.attr('data-noresult');
                        markup += '
' + noResult + '

';
                    }

                    $wrap.append(markup);
                    preview = $('.searchSkinObjectPreview', $wrap);

                    //attach click event
                    $('li', preview).on('click', function () {
                        var navigateUrl = $(this).attr('data-url');
                        if (navigateUrl) {
                            window.location.href = navigateUrl;
                        }
                        return false;
                    });

                    //attach see more
                    $('.searchSkinObjectPreview_more', $wrap).on('click', function () {
	                    var $searchButton = $wrap.parents('.SearchContainer').length ? $wrap.parent().next() : $wrap.next();
                        var href = $searchButton.attr('href');
                        var code = href.replace('javascript:', '');
                        eval(code);
                        return false;
                    });
                };

                $('.searchInputContainer a.dnnSearchBoxClearText').on('click', function () {
                    var $this = $(this);
                    var $wrap = $this.parent();
                    $('.searchInputContainer input').val('').focus();
                    $this.removeClass('dnnShow');
                    $('.searchSkinObjectPreview', $wrap).remove();
                    return false;
                });

                $('.searchInputContainer').next().on('click', function() {
                    var $this = $(this);
                    var inputBox = $this.prev().find('input[type="text"]');
                    var val = inputBox.val();
                    if (val.length) {
                        return true;
                    }
                    return false;
                });

                $('.searchInputContainer input').on('keyup', function(e) {
                    var k = e.keyCode || e.witch;
                    if ($.inArray(k, self._ignoreKeyCodes) > -1) return;

                    var $this = $(this);
                    var $wrap = $this.parent();
                    var val = $this.val();
                    var container = $this.parent('.searchInputContainer');
                    if (!val) {

                        $('a.dnnSearchBoxClearText', $wrap).removeClass('dnnShow');
                        $('.searchSkinObjectPreview', $wrap).remove();
                    } else {
                        $('a.dnnSearchBoxClearText', $wrap).addClass('dnnShow');

                        if (self.settings.searchType != 'S' ||
                            val.length < self.settings.minCharRequiredTriggerAutoSearch) return;

                        if (throttle) {
                            clearTimeout(throttle);
                            delete throttle;
                        }

                        throttle = setTimeout(function() {
                            var service = $.dnnSF ? $.dnnSF(-1) : null;
                            var url = makeUrl(val, service);
                            if (url) {
                                $.ajax({
                                    url: url,
                                    beforeSend: service ? service.setModuleHeaders : null,
                                    success: function(result) {
                                        if (result)
                                            generatePreviewTemplate(result, container);
                                    },
                                    error: function() {
                                    },
                                    type: 'GET',
                                    dataType: 'json',
                                    contentType: "application/json"
                                });
                            }
                        }, self.settings.delayTriggerAutoSearch);
                    }
                })
                .on('paste', function() {
                    $(this).triggerHandler('keyup');
                })
                .on('keypress', function(e) {
                    var k = e.keyCode || e.which;
                    if (k == 13) {
                        var $this = $(this);
                        var $wrap = $this.parent();
                        var val = $this.val();
                        if (val.length) {
                            var href = $wrap.next().attr('href');
                            if (!href) {
                                // dropdown search
                                href = $wrap.parent().next().attr('href');
                            }
                            var code = href.replace('javascript:', '');
                            eval(code);
                            e.preventDefault();
                        } else {
                            e.preventDefault();
                        }
                    }
                });
            }
        };
    }
})(jQuery);
<commit_msg>DNN-19842: avoid strict mode from jquery. (#2039)<commit_after>(function ($) {
    if (typeof dnn == 'undefined') window.dnn = {};
    if (typeof dnn.searchSkinObject == 'undefined') {
        dnn.searchSkinObject = function (options) {
            var settings = {
                delayTriggerAutoSearch: 100,
                minCharRequiredTriggerAutoSearch: 2,
                searchType: 'S',
                enableWildSearch: true,
                cultureCode: 'en-US'
            };
            this.settings = $.extend({}, settings, options);
        };
        dnn.searchSkinObject.prototype = {
            _ignoreKeyCodes: [9, 13, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45],
            init: function () {
                var throttle = null, self = this;
                var makeUrl = function (val, service) {
                    var url = service ? service.getServiceRoot('internalservices') + 'searchService/preview' : null;
                    if (!url) return null;
                    var params = {};
                    params['keywords'] = val.replace(/^\s+|\s+$/g, '');
                    if (!self.settings.enableWildSearch) params["forceWild"] = "0";
                    params['culture'] = self.settings.cultureCode;
                    if (self.settings.portalId >= 0)
                        params['portal'] = self.settings.portalId;
                    var urlAppend = [];
                    $.each(params, function (index, value) {
                        urlAppend.push([index, encodeURIComponent(value)].join('='));
                    });

                    if (urlAppend.length) {
                        url += url.indexOf('?') === -1 ? '?' : '&';
                        url += urlAppend.join('&');
                    }
                    return url;
                };

                var generatePreviewTemplate = function (data, $wrap) {
                    var preview = $('.searchSkinObjectPreview', $wrap);
                    if (preview.length)
                        preview.remove();

                    var markup = '
';
                    if (data && data.length) {
                        for (var i = 0; i < data.length; i++) {
                            var group = data[i];
                            if (group.Results && group.Results.length) {
                                var groupTitle = group.DocumentTypeName;
                                markup += '
' + groupTitle + '
';
                                for (var j = 0; j < group.Results.length; j++) {
                                    var item = group.Results[j];
                                    var itemTitle = item.Title;
                                    var itemUrl = item.DocumentUrl;
                                    var itemDescription = item.Description;
                                    var itemSnippet = item.Snippet;
                                    markup += '
';
                                    if (item.Attributes.Avatar) {
                                        markup += '';
                                    }
                                    markup += '' + itemTitle + '';
                                    if (itemDescription) {
                                        markup += '
' + itemDescription + '
';
                                    }
                                    if (itemSnippet) {
                                        markup += '
' + itemSnippet + '
';
                                    }
                                    markup += '
';
                                } // end for group items
                            }
                        } // end for group

                        var moreResults = $wrap.attr('data-moreresults');
                        markup += '
' + moreResults + '
';
                        markup += '
';
                    }
                    else {
                        var noResult = $wrap.attr('data-noresult');
                        markup += '
' + noResult + '

';
                    }

                    $wrap.append(markup);
                    preview = $('.searchSkinObjectPreview', $wrap);

                    //attach click event
                    $('li', preview).on('click', function () {
                        var navigateUrl = $(this).attr('data-url');
                        if (navigateUrl) {
                            window.location.href = navigateUrl;
                        }
                        return false;
                    });

                    //attach see more
                    $('.searchSkinObjectPreview_more', $wrap).on('click', function () {
                        var $searchButton = $wrap.next();
                        if (!$searchButton.length) {
                            $searchButton = $wrap.parent().next();
                        }
                        $searchButton[0].click();
                        return false;
                    });
                };

                $('.searchInputContainer a.dnnSearchBoxClearText').on('click', function () {
                    var $this = $(this);
                    var $wrap = $this.parent();
                    $('.searchInputContainer input').val('').focus();
                    $this.removeClass('dnnShow');
                    $('.searchSkinObjectPreview', $wrap).remove();
                    return false;
                });

                $('.searchInputContainer').next().on('click', function() {
                    var $this = $(this);
                    var inputBox = $this.prev().find('input[type="text"]');
                    var val = inputBox.val();
                    if (val.length) {
                        return true;
                    }
                    return false;
                });

                $('.searchInputContainer input').on('keyup', function(e) {
                    var k = e.keyCode || e.witch;
                    if ($.inArray(k, self._ignoreKeyCodes) > -1) return;

                    var $this = $(this);
                    var $wrap = $this.parent();
                    var val = $this.val();
                    var container = $this.parent('.searchInputContainer');
                    if (!val) {

                        $('a.dnnSearchBoxClearText', $wrap).removeClass('dnnShow');
                        $('.searchSkinObjectPreview', $wrap).remove();
                    } else {
                        $('a.dnnSearchBoxClearText', $wrap).addClass('dnnShow');

                        if (self.settings.searchType != 'S' ||
                            val.length < self.settings.minCharRequiredTriggerAutoSearch) return;

                        if (throttle) {
                            clearTimeout(throttle);
                            delete throttle;
                        }

                        throttle = setTimeout(function() {
                            var service = $.dnnSF ? $.dnnSF(-1) : null;
                            var url = makeUrl(val, service);
                            if (url) {
                                $.ajax({
                                    url: url,
                                    beforeSend: service ? service.setModuleHeaders : null,
                                    success: function(result) {
                                        if (result)
                                            generatePreviewTemplate(result, container);
                                    },
                                    error: function() {
                                    },
                                    type: 'GET',
                                    dataType: 'json',
                                    contentType: "application/json"
                                });
                            }
                        }, self.settings.delayTriggerAutoSearch);
                    }
                })
                .on('paste', function() {
                    $(this).triggerHandler('keyup');
                })
                .on('keypress', function(e) {
                    var k = e.keyCode || e.which;
                    if (k == 13) {
                        var $this = $(this);
                        var $wrap = $this.parent();
                        var val = $this.val();
                        if (val.length) {
                            var $searchButton = $wrap.next();
                            if (!$searchButton.length) {
                                $searchButton = $wrap.parent().next();
                            }
                            $searchButton[0].click();
                            e.preventDefault();
                        } else {
                            e.preventDefault();
                        }
                    }
                });
            }
        };
    }
})(jQuery);
<|endoftext|>


<commit_before>/**
 * Sitespeed.io - How speedy is your site? (http://www.sitespeed.io)
 * Copyright (c) 2014, Peter Hedenskog, Tobias Lidskog
 * and other contributors
 * Released under the Apache 2.0 License
 */
var path = require('path'),
  childProcess = require('child_process'),
  binPath = require('phantomjs').path,
  util = require('../util/util'),
  fs = require('fs'),
  winston = require('winston'),
  async = require('async');

/**
 * Analyze a page using YSlow
 */
module.exports = {
  analyze: function(urls, config, callback) {
    var log = winston.loggers.get('sitespeed.io');
    var ySlowResults = path.join(config.run.absResultDir, config.dataDir, 'yslow');
    fs.mkdir(ySlowResults, function(err) {

      if (err) {
        log.log('error', 'Couldn\'t create YSlow result dir:' + ySlowResults + ' ' + err);
        callback(err, {
          'type': 'yslow',
          'data': {},
          'errors': {}
        });
      } else {

        var queue = async.queue(analyzeUrl, config.threads);

        var errors = {};
        var pageData = {};
        urls.forEach(function(u) {
          queue.push({
            'url': u,
            'config': config
          }, function(data, err) {
            if (err) {
              errors[u] = err;
            } else {
              pageData[u] = data;
            }
          });
        });

        queue.drain = function() {
          callback(undefined, {
            'type': 'yslow',
            'data': pageData,
            'errors': errors
          });
        };
      }
    });
  }
};

function analyzeUrl(args, asyncDoneCallback) {
  var url = args.url;
  var config = args.config;
  var log = winston.loggers.get('sitespeed.io');

  // PhantomJS arguments
  var childArgs = ['--ssl-protocol=any', '--ignore-ssl-errors=yes'];

  if (config.proxy) {
    childArgs.push('--proxy', config.urlProxyObject.host, '--proxy-type',
      config.urlProxyObject.protocol);
  }

  // arguments to YSlow
  childArgs.push(path.join(__dirname, '..', config.yslow), '-d', '-r', config.ruleSet,
    '--ua', config.userAgent);

  childArgs.push('-c', '1');

  if (config.basicAuth) {
    childArgs.push('-ba', config.basicAuth);
  }

  if (config.cdns) {
    childArgs.push('--cdns', config.cdns.join(','));
  }

  if (config.requestHeaders) {
    childArgs.push('-ch', JSON.stringify(config.requestHeaders));
  }

  var resultsPath = path.join(config.run.absResultDir, config.dataDir, 'yslow', util.getFileName(url) +
    '-yslow.json');
  childArgs.push('--file', resultsPath);

  childArgs.push('-vp', config.viewPort);

  childArgs.push(url);

  log.log('info', 'Running YSlow for ' + url +  ' ' + childArgs);

  childProcess.execFile(binPath, childArgs, {
    timeout: 240000
  }, function(err, stdout, stderr) {

    if (stderr) {
      log.log('error', 'stderr: Error running YSlow: ' + url);
    }
    // TODO we could maybe check the err but today the YSLOW
    // fails, sending error even if it works sometiomes,
    // think it is my fault though /peter :)
    {
      var file = path.join(config.run.absResultDir, config.dataDir, 'yslow',
        util.getFileName(url) + '-yslow.json');

      fs.readFile(file, function(error, data) {
        if (error) {
          log.log('error', 'The YSlow file was not created:' + file);
          asyncDoneCallback(undefined, error);
        } else {
          var yslow;
          try {
            yslow = JSON.parse(data);
            yslow.originalUrl = url;
            /**
             Ok, here's one thing that we have not been able to fix,
             for some reasons sometimes the component array is returned as a String.
             Would be nice to understand why, this is just a quick fix.
             */
            if (!Array.isArray(yslow.comps)) {
              yslow.comps = JSON.parse(yslow.comps);
            }
          } catch (e) {
            err = e;
          }
          asyncDoneCallback(yslow, err);
        }
      });
    }
  });
}
<commit_msg>don't log yslow args<commit_after>/**
 * Sitespeed.io - How speedy is your site? (http://www.sitespeed.io)
 * Copyright (c) 2014, Peter Hedenskog, Tobias Lidskog
 * and other contributors
 * Released under the Apache 2.0 License
 */
var path = require('path'),
  childProcess = require('child_process'),
  binPath = require('phantomjs').path,
  util = require('../util/util'),
  fs = require('fs'),
  winston = require('winston'),
  async = require('async');

/**
 * Analyze a page using YSlow
 */
module.exports = {
  analyze: function(urls, config, callback) {
    var log = winston.loggers.get('sitespeed.io');
    var ySlowResults = path.join(config.run.absResultDir, config.dataDir, 'yslow');
    fs.mkdir(ySlowResults, function(err) {

      if (err) {
        log.log('error', 'Couldn\'t create YSlow result dir:' + ySlowResults + ' ' + err);
        callback(err, {
          'type': 'yslow',
          'data': {},
          'errors': {}
        });
      } else {

        var queue = async.queue(analyzeUrl, config.threads);

        var errors = {};
        var pageData = {};
        urls.forEach(function(u) {
          queue.push({
            'url': u,
            'config': config
          }, function(data, err) {
            if (err) {
              errors[u] = err;
            } else {
              pageData[u] = data;
            }
          });
        });

        queue.drain = function() {
          callback(undefined, {
            'type': 'yslow',
            'data': pageData,
            'errors': errors
          });
        };
      }
    });
  }
};

function analyzeUrl(args, asyncDoneCallback) {
  var url = args.url;
  var config = args.config;
  var log = winston.loggers.get('sitespeed.io');

  // PhantomJS arguments
  var childArgs = ['--ssl-protocol=any', '--ignore-ssl-errors=yes'];

  if (config.proxy) {
    childArgs.push('--proxy', config.urlProxyObject.host, '--proxy-type',
      config.urlProxyObject.protocol);
  }

  // arguments to YSlow
  childArgs.push(path.join(__dirname, '..', config.yslow), '-d', '-r', config.ruleSet,
    '--ua', config.userAgent);

  childArgs.push('-c', '1');

  if (config.basicAuth) {
    childArgs.push('-ba', config.basicAuth);
  }

  if (config.cdns) {
    childArgs.push('--cdns', config.cdns.join(','));
  }

  if (config.requestHeaders) {
    childArgs.push('-ch', JSON.stringify(config.requestHeaders));
  }

  var resultsPath = path.join(config.run.absResultDir, config.dataDir, 'yslow', util.getFileName(url) +
    '-yslow.json');
  childArgs.push('--file', resultsPath);

  childArgs.push('-vp', config.viewPort);

  childArgs.push(url);

  log.log('info', 'Running YSlow for ' + url);

  childProcess.execFile(binPath, childArgs, {
    timeout: 240000
  }, function(err, stdout, stderr) {

    if (stderr) {
      log.log('error', 'stderr: Error running YSlow: ' + url);
    }
    // TODO we could maybe check the err but today the YSLOW
    // fails, sending error even if it works sometiomes,
    // think it is my fault though /peter :)
    {
      var file = path.join(config.run.absResultDir, config.dataDir, 'yslow',
        util.getFileName(url) + '-yslow.json');

      fs.readFile(file, function(error, data) {
        if (error) {
          log.log('error', 'The YSlow file was not created:' + file);
          asyncDoneCallback(undefined, error);
        } else {
          var yslow;
          try {
            yslow = JSON.parse(data);
            yslow.originalUrl = url;
            /**
             Ok, here's one thing that we have not been able to fix,
             for some reasons sometimes the component array is returned as a String.
             Would be nice to understand why, this is just a quick fix.
             */
            if (!Array.isArray(yslow.comps)) {
              yslow.comps = JSON.parse(yslow.comps);
            }
          } catch (e) {
            err = e;
          }
          asyncDoneCallback(yslow, err);
        }
      });
    }
  });
}
<|endoftext|>


<commit_before>/* Timer.js
KC3改 Timer Object

Contains a single timer which contains number of seconds left
Has functions for TimerManager to use
*/
(function(){
	"use strict";

	window.KC3Timer = function(element, type, num){
		this.alerted = false;
		this.type = type;
		this.num = num;
		this.element = element;
		this.deactivate();
	};

	KC3Timer.prototype.show = function(element){ this.element.show(); };
	KC3Timer.prototype.hide = function(element){ this.element.hide(); };

	KC3Timer.prototype.activate = function(completion, faceId, expedNum){
		this.active = true;
		this.completion = completion;
		if(typeof faceId != "undefined"){ if(faceId>0){ this.faceId = faceId; } }
		if(typeof expedNum != "undefined"){ this.expedNum = expedNum; }

		var remaining = this.completion - (new Date()).getTime();
		remaining = Math.ceil((remaining - (ConfigManager.alert_diff*1000))/1000);
		if(remaining <= 0){
			this.alerted = true;
		}
	};

	KC3Timer.prototype.deactivate = function(){
		this.active = false;
		this.completion = 0;
		this.faceId = 0;
		this.expedNum = 0;
		$(".timer-img img", this.element).attr("src", "../../../../assets/img/ui/empty.png");
		$(".timer-expnum", this.element).text("");
		$(".timer-time", this.element).text("");
	};

	KC3Timer.prototype.time = function(){
		$(".timer-time", this.element).text( this.text() );
	};

	KC3Timer.prototype.expnum = function(){
		if(this.expedNum > 0){
			$(".timer-expnum", this.element).text( this.expedNum );
		}
	};

	KC3Timer.prototype.face = function( faceId){
		if(typeof faceId != "undefined"){ this.faceId = faceId; }
		if(this.faceId > 0){
			$(".timer-img img", this.element).attr("src", KC3Meta.shipIcon(this.faceId, "../../../../assets/img/ui/empty.png"));
			$(".timer-img", this.element).attr("title", KC3Meta.shipName( KC3Master.ship(this.faceId).api_name ) );
		}
	};

	KC3Timer.prototype.updateElement = function(element){
		this.element = element;
	};

	KC3Timer.prototype.text = function(){
		if(this.active){
			var remaining = this.completion - (new Date()).getTime();
			var timerAllowance = (this.type == 2)?0:ConfigManager.alert_diff;
			remaining = Math.ceil((remaining - (timerAllowance*1000))/1000);
			if(remaining > 0){
				this.alerted = false;

				var hrs = Math.floor(remaining/3600);
				remaining = remaining - (hrs * 3600);
				if(hrs < 10){ hrs = "0"+hrs; }

				var min = Math.floor(remaining/60);
				remaining = remaining - (min * 60);
				if(min < 10){ min = "0"+min; }

				if(remaining < 10){ remaining = "0"+remaining; }

				return hrs+":"+min+":"+remaining;
			}else{
				this.completionAlert();
				return "Complete!";
			}
		}else{
			return "";
		}
	};

	KC3Timer.prototype.completionAlert = function(){
		if(this.alerted){ return false; }
		this.alerted = true;

		// Sound Alerts
		var notifSound;
		switch(ConfigManager.alert_type){
			case 1: notifSound = new Audio("../../../../assets/snd/ding.mp3"); break;
			case 2: notifSound = new Audio(ConfigManager.alert_custom); break;
			default: notifSound = false; break;
		}
		if(notifSound){
			notifSound.volume = ConfigManager.alert_volume / 100;
			notifSound.play();
		}

		// Desktop notification
		if(ConfigManager.alert_desktop){
			var notifData = { type: "basic" };
			var shipName;

			// Notification types show varying messages
			switch(this.type){
				case 0:
					var thisFleet = PlayerManager.fleets[this.num+1];
					notifData.title = "Expedition Complete!";
					notifData.message = "Fleet "+(this.num+2)+" just arrived from Expedition #"+thisFleet.mission[1];
					notifData.iconUrl = "../../assets/img/quests/expedition.jpg";
					break;
				case 1:
					shipName = KC3ShipManager.get( PlayerManager.repairShips[this.num] ).name();
					notifData.title = "Repairs Complete!";
					notifData.message = shipName+" is out of the repair dock!";
					notifData.iconUrl = "../../assets/img/quests/supply.jpg";
					break;
				case 2:
					shipName = KC3Meta.shipName( KC3Master.ship( this.faceId ).api_name );
					notifData.title = "Construction Complete!";
					if(ConfigManager.info_face){
						notifData.message = "New face "+shipName+" has been constructed!";
					}else{
						notifData.message = "A newface is ready to see you in the construction docks!";
					}
					notifData.iconUrl = "../../assets/img/quests/build.jpg";
					break;
				default:break;
			}

			// Tell background page to show the notification, cant do it here
			(new RMsg("service", "notify_desktop", {
				notifId: this.type+"_"+this.num,
				data: notifData
			})).execute();
		}

	};

})();<commit_msg>Additional Fix for #393<commit_after>/* Timer.js
KC3改 Timer Object

Contains a single timer which contains number of seconds left
Has functions for TimerManager to use
*/
(function(){
	"use strict";

	window.KC3Timer = function(element, type, num){
		this.alerted = false;
		this.type = type;
		this.num = num;
		this.element = element;
		this.deactivate();
	};

	KC3Timer.prototype.show = function(element){ this.element.show(); };
	KC3Timer.prototype.hide = function(element){ this.element.hide(); };

	KC3Timer.prototype.activate = function(completion, faceId, expedNum){
		this.active = true;
		this.completion = completion;
		if(typeof faceId != "undefined"){ if(faceId>0){ this.faceId = faceId; } }
		if(typeof expedNum != "undefined"){ this.expedNum = expedNum; }

		var remaining = this.completion - (new Date()).getTime();
		remaining = Math.ceil((remaining - (ConfigManager.alert_diff*1000))/1000);
		if(remaining <= 0){
			this.alerted = true;
		}
	};

	KC3Timer.prototype.deactivate = function(){
		this.active = false;
		this.completion = 0;
		this.faceId = 0;
		this.expedNum = 0;
		$(".timer-img img", this.element).attr("src", "../../../../assets/img/ui/empty.png");
		$(".timer-expnum", this.element).text("");
		$(".timer-time", this.element).text("");
	};

	KC3Timer.prototype.time = function(){
		$(".timer-time", this.element).text( this.text() );
	};

	KC3Timer.prototype.expnum = function(){
		if(this.expedNum > 0){
			$(".timer-expnum", this.element).text( this.expedNum );
		}
	};

	KC3Timer.prototype.face = function( faceId){
		if(typeof faceId != "undefined"){ this.faceId = faceId; }
		if(this.faceId > 0){
			$(".timer-img img", this.element).attr("src", KC3Meta.shipIcon(this.faceId, "../../../../assets/img/ui/empty.png"));
			$(".timer-img", this.element).attr("title", KC3Meta.shipName( KC3Master.ship(this.faceId).api_name ) );
		}
	};

	KC3Timer.prototype.updateElement = function(element){
		this.element = element;
	};

	KC3Timer.prototype.text = function(){
		if(this.active){
			var remaining = this.completion - (new Date()).getTime();
			var timerAllowance = (this.type == 2)?0:ConfigManager.alert_diff;
			remaining = Math.ceil((remaining - (timerAllowance*1000))/1000);
			if(remaining > 0){
				this.alerted = false;

				var hrs = Math.floor(remaining/3600);
				remaining = remaining - (hrs * 3600);
				if(hrs < 10){ hrs = "0"+hrs; }

				var min = Math.floor(remaining/60);
				remaining = remaining - (min * 60);
				if(min < 10){ min = "0"+min; }

				if(remaining < 10){ remaining = "0"+remaining; }

				return hrs+":"+min+":"+remaining;
			}else{
				this.completionAlert();
				return "Complete!";
			}
		}else{
			return "";
		}
	};

	KC3Timer.prototype.completionAlert = function(){
		if(this.alerted){ return false; }
		this.alerted = true;

		// Sound Alerts
		var notifSound;
		switch(ConfigManager.alert_type){
			case 1: notifSound = new Audio("../../../../assets/snd/ding.mp3"); break;
			case 2: notifSound = new Audio(ConfigManager.alert_custom); break;
			default: notifSound = false; break;
		}
		if(notifSound){
			notifSound.volume = ConfigManager.alert_volume / 100;
			notifSound.play();
		}

		// Desktop notification
		if(ConfigManager.alert_desktop){
			var notifData = { type: "basic" };
			var shipName;

			// Notification types show varying messages
			switch(this.type){
				case 0:
					var thisFleet = PlayerManager.fleets[this.num+1];
					notifData.title = "Expedition Complete!";
					notifData.message = "Fleet "+(this.num+2)+" just arrived from Expedition #"+thisFleet.mission[1];
					notifData.iconUrl = "../../assets/img/quests/expedition.jpg";
					break;
				case 1:
					shipName = KC3ShipManager.get( PlayerManager.repairShips[this.num+1] ).name();
					notifData.title = "Repairs Complete!";
					notifData.message = shipName+" is out of the repair dock!";
					notifData.iconUrl = "../../assets/img/quests/supply.jpg";
					break;
				case 2:
					shipName = KC3Meta.shipName( KC3Master.ship( this.faceId ).api_name );
					notifData.title = "Construction Complete!";
					if(ConfigManager.info_face){
						notifData.message = "New face "+shipName+" has been constructed!";
					}else{
						notifData.message = "A newface is ready to see you in the construction docks!";
					}
					notifData.iconUrl = "../../assets/img/quests/build.jpg";
					break;
				default:break;
			}

			// Tell background page to show the notification, cant do it here
			(new RMsg("service", "notify_desktop", {
				notifId: this.type+"_"+this.num,
				data: notifData
			})).execute();
		}

	};

})();<|endoftext|>