<commit_before>/* eslint-disable no-underscore-dangle */
'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _bluebird = require('bluebird');

var _bluebird2 = _interopRequireDefault(_bluebird);

var Plan = (function () {
  function Plan() {
    _classCallCheck(this, Plan);

    this.steps = [];
  }

  //  A PlanStep represents one step in the process to load and cache data. It
  //  is created by the Plan.addStepForProperty method.

  // Call this method to add a query to fetch and cache the data entities for
  // the referenced property. It may or may not create a plan step, depending
  // on whether an existing plan step can satisfy the retrieval.

  _createClass(Plan, [{
    key: 'addStepForProperty',
    value: function addStepForProperty(entityName, service, property) {

      // Split the path by the '.' characters into an array
      var propertyChain = property.split('.');

      // See if a plan step exists for this entity
      var planStep = _lodash2['default'].find(this.steps, 'entity', entityName);

      // If we didnt find one, create one and add it
      if (!planStep) {
        planStep = new PlanStep(entityName, service);
        this.steps.push(planStep);
      }

      // Add the property path if it doesn't exist
      if (planStep.paths.indexOf(property) === -1) {
        planStep.paths.push(property);
      }

      // Update the depth
      planStep.depth = Math.max(planStep.depth, propertyChain.length);

      // ensure they are sorted
      this.steps = _lodash2['default'].sortByAll(this.steps, ['depth', 'entity']);
    }
  }, {
    key: 'findStepsForDepth',
    value: function findStepsForDepth(depth) {
      return _lodash2['default'].filter(this.steps, function (step) {
        return step.depth === depth;
      });
    }

    // Execute the query plan using the specified items.
  }, {
    key: 'execute',
    value: function execute(items) {

      var planContext = {
        items: items,
        plan: this
      };

      // Build a chain of all the promises, starting with the plan context
      var promises = _lodash2['default'].reduce(this.steps, function (previous, step) {
        return previous.bind(step).then(step.execute);
      }, _bluebird2['default'].resolve(planContext));
      return promises.bind();
    }
  }]);

  return Plan;
})();

exports.Plan = Plan;

var PlanStep = (function () {
  function PlanStep(entity, service) {
    _classCallCheck(this, PlanStep);

    this.entity = entity; // The name of the entity, such as Account
    this.service = service; // A reference to the actual service
    this.paths = []; // An array of property reference paths
    //
    // paths: [
    //       'site.instructor',
    //       'site.grantee.administrator',
    //       'site.grantee.region.administrator'
    //     ],
    this.depth = 1; // The execution depth. This determines the order
    // in which the steps are executed. Plan steps
    // with the same value will be executed
    // concurrently.
  }

  // Returns a promise that performs the operations necessary to load the data
  // associated with this step. Be sure to bind the this argument to this step
  // instance. It assumes the incoming argument is a plan context and also
  // ensures the plan context is continued down the promise chain.

  _createClass(PlanStep, [{
    key: 'execute',
    value: function execute(planContext) {
      var _this = this;

      var stepContext = {
        planContext: planContext,
        step: this
      };

      return _bluebird2['default'].resolve(stepContext).then(function (sc) {
        // Get the ids
        var ids = _this.getPropertyValues(sc.planContext.items, sc.step);
        // Filter out any values that are already cached
        ids = sc.step.service.uncachedIds(ids);
        // Query and cache the entities
        return sc.step.service.loadByIds(ids);
      }).then(function (results) {
        return planContext;
      });
    }

    //  Returns all the unique values for the properties specified in a query
    //  plan step's path argument. Items must be an array of items and step must
    //  be a valid step.
    //
    //   {
    //     entity: 'Account',
    //     service: AccountService,
    //     paths: [
    //       'site'.instructor',
    //       'site.grantee.administrator',
    //       'site.grantee.region.administrator'
    //     ],
    //     depth: 3
    //   }
  }, {
    key: 'getPropertyValues',
    value: function getPropertyValues(items, step) {
      var _this2 = this;

      // Initialize the id list that will be used to collect the values
      var ids = [];

      // For each item in the list, get the values
      items.forEach(function (item) {
        // Loop over the paths
        step.paths.forEach(function (path) {
          var pathChain = path.split('.');
          // Get all the id values
          _this2.collectPropertyValues(item, pathChain, ids);
        });
      });

      return _lodash2['default'].uniq(ids);
    }
  }, {
    key: 'collectPropertyValues',
    value: function collectPropertyValues(item, propertyChain, ids) {
      var _this3 = this;

      // Get the property we are extracting and update the property chain to
      // exclude the property we are currently processing.
      var property = propertyChain[0];
      propertyChain = propertyChain.slice(1); // Without the first element

      // If the propertyChain is empty, we know we are at the place we need to
      // collect the value.
      if (propertyChain.length === 0) {

        // Get the key from the lookup table
        var key = item._rdsReferences[property];
        // attempt to get the corresponding value
        var value = item[key];

        // Only process values that are defined
        if (value !== null && !_lodash2['default'].isUndefined(value)) {
          // Add the value to the found ids
          if (Array.isArray(value)) {
            value.forEach(function (val) {
              ids.push(val);
            });
          } else {
            ids.push(value);
          }
        }
      } else {
        // Get the property value from the getter/setter
        var value = item[property];

        // Only process values that are defined
        if (value !== null && !_lodash2['default'].isUndefined(value)) {
          // If we are not at the property yet, keep traversing
          if (Array.isArray(value)) {
            value.forEach(function (val) {
              _this3.collectPropertyValues(val, propertyChain, ids);
            });
          } else {
            this.collectPropertyValues(value, propertyChain, ids);
          }
        }
      }
    }
  }]);

  return PlanStep;
})();

exports.PlanStep = PlanStep;

var ReferentialDataService = (function () {
  function ReferentialDataService($log, $injector) {
    _classCallCheck(this, ReferentialDataService);

    // This types dictionary contains a key per type. Each key is a reference to
    // another dictionary. Here is an example:
    //
    // Class: {                                 <-- entityName
    //    _typeName: 'Class'                    <-- entityName
    //    'classType': {                        <-- propertyName
    //      propertyName: 'classType',          <-- key name without Id
    //      serviceName: 'classTypeService',    <-- name of the reference service
    //      service: classTypeService,          <-- Looked up after initialized
    //      keyName: 'classTypeId',
    //      propertyTypeName: 'ClassType',      <-- ref entityName
    //      property: {
    //        get: function() { ... },
    //        set: function(value) { ... }
    //      }
    //    }
    //  }

    this.$log = $log;
    this.$injector = $injector;
    this._types = {};
  }

  // Registers a foreign get reference. This information is used to create
  // getters and setters on model entities to reference related objects. It is
  // also used to load the referential data as well. To register a reference,
  // call this method with the following arguments
  //
  //  * typeName: the type name (class name)
  //  * keyName: the name of the property that holds the key reference(s)
  //      the key itself can be a single value or an array of values
  //  * serviceName: the related service name,
  //  * propertyTypeName: the referenced type.
  //  * propertyName: an optional name for the getter/setter defaults to
  //        the keyName minus the last two characters so it turns
  //        'propertyId' to 'property'.
  //
  //   NOTE: if your keyname is `propertyIds` you should provide a propertyName
  //     otherwise your property name will be `propertyI` when you
  //     probably want `properties`

  _createClass(ReferentialDataService, [{
    key: 'registerReference',
    value: function registerReference(typeName, keyName, serviceName, propertyTypeName, propertyName) {

      // Get the type definition or define it if not found
      var type = this._types[typeName];
      if (!type) {
        type = { _typeName: typeName };
        this._types[typeName] = type;
      }

      // Get the property name if not specifically assigned
      if (!propertyName) {
        propertyName = keyName.slice(0, -2);
      }

      // Get the reference definition or define it if not found
      var reference = type[propertyName];
      if (!reference) {
        reference = {};
        type[propertyName] = reference;
      }

      // Set the values of the reference
      reference.propertyName = propertyName;
      reference.keyName = keyName;
      reference.serviceName = serviceName;
      reference.propertyTypeName = propertyTypeName;

      // Create the getter and setter definition
      reference.property = {
        enumerable: false,
        get: function get() {
          return reference.service.get(this[reference.keyName]);
        },
        set: function set(value) {
          if (value) {
            this[reference.keyName] = value.key;
          } else {
            this[reference.keyName] = value;
          }
        }
      };
    }

    // In order to add the getter/setter methods for referential properties, this
    // method must be called on each model instance created. It adds the getter/
    // setter methods.
  }, {
    key: 'configureClass',
    value: function configureClass(typeName, obj) {
      var _this4 = this;

      // Get the references for the type. If there are no references for the
      // type, just return.
      var type = this._types[typeName];
      if (!type) {
        return;
      }

      // Add the actual getter/setter properties to the class instance
      var referenceNames = Object.keys(type);
      //initialize a lookup table for use later.
      obj._rdsReferences = {};

      referenceNames.forEach(function (referenceName) {
        if (referenceName !== '_typeName') {
          var reference = type[referenceName];
          // Ensure the service instance is injected
          if (!reference.service) {
            reference.service = _this4.$injector.get(reference.serviceName);
            if (!reference.service) {
              throw new Error('The reference property ' + reference.propertyName + ' on ' + typeName + '\n              requires service {$reference.serviceName} but it was not found');
            }
          }
          Object.defineProperty(obj, reference.propertyName, reference.property);
          //create a lookup on the object for use later when executing the query plan
          obj._rdsReferences[reference.propertyName] = reference.keyName;
        }
      });
    }
  }, {
    key: 'loadReferential',
    value: function loadReferential(pagedItems, type, properties) {
      // Build the query plan
      var plan = this.buildQueryPlan(type, properties);

      // Return a promise for the execution of the query plan. Also,
      // return the original items if successful.
      return plan.execute(pagedItems.items).then(function (planContext) {
        return pagedItems;
      });
    }

    // Fetch all the properties and sort them in ascenting order. This will ensure
    // child properties are processed after the parent. For example, site
    // would be processed before site.grantee and site.grantee.region
    // after that.
    //
    // An Array of Classes (ClassService)
    //   site (SiteService)
    //   site.instructor (AccountService)
    //   site.grantee.administrator (AccountService)
    //   site.grantee.region (RegionService)
    //   site.grantee.region.administrator (AccountService)
    //   classType (ClassTypeService)
    //   classRooms (ClassRoomService)
    //
    //
    // This should result in six queries, one for each of the entities: Class,
    // ClassRooms, Site, Region, ClassType, and Account. The should be executed
    // in the following order with the entities in [] executed in parallel:
    //
    //   Class (Already queried) -> [ClassRoom, Site, ClassType], Region, Account
    //
    // Loop over the tree and get the service types. For each type, determine the
    // maximum depth, the number of '.' in the path. Order the entities by depth.
    // An example of the data structure for the services in the example above is:
    //
    // {
    //   steps: [
    //     {
    //       entity: 'Site',
    //       serviceName: 'siteService',
    //       paths: [
    //          'site'
    //       ],
    //       depth: 1
    //     },
    //     {
    //       entity: 'ClassType',
    //       serviceName: 'classTypeService',
    //       paths: [
    //          'classType'
    //       ],
    //       depth: 1
    //     },
    //     {
    //       entity: 'ClassRoom',
    //       serviceName: 'classRoomService',
    //       paths: [
    //          'classRooms'
    //       ],
    //       depth: 1
    //     },
    //     {
    //       entity: 'Region',
    //       serviceName: 'regionService',
    //       paths: [
    //          'site.grantee.region'
    //       ],
    //       depth: 2
    //     },
    //     {
    //       entity: 'Account',
    //       serviceName: 'accountService',
    //       paths: [
    //         'site.instructor',
    //         'site.grantee.administrator',
    //         'site.grantee.region.administrator'
    //       ],
    //       depth: 3
    //     }
    //   ]
    // }
    //
    // To process them, makes sure they are sorted in order by depth. Loop over
    // the depths and process the entities at each depth in parallel.
    //
    //  for each depth d {
    //    for each entity e at depth {
    //      traverse the paths for the entity and collect all the ids
    //      get the distinct list of ids
    //      check each id and remove it if it is already cached
    //      make the request with a then that caches the results and return the
    //        promise
    //    }
    //  }

    // Takes in an array of entities to query and builds the query plan. An
    // example input could be:
    //
    //   site (SiteService)
    //   site.instructor (AccountService)
    //   site.grantee.administrator (AccountService)
    //   site.grantee.region (RegionService)
    //   site.grantee.region.administrator (AccountService)
    //   classType (ClassTypeService)
    //   classRooms (ClassRoomService)
    //
    // It returns a plan that can be used to execute
  }, {
    key: 'buildQueryPlan',
    value: function buildQueryPlan(rootType, propertyList) {
      var _this5 = this;

      var plan = new Plan();

      propertyList.forEach(function (property) {

        // Add the property and all sub properties. For example, you add
        // site.grantee.region, be sure site and site.grantee are
        // in the list.
        var propertyChain = property.split('.');
        for (var i = 0; i < propertyChain.length; i++) {
          var currentProperty = propertyChain.slice(0, i + 1).join('.');
          // Follow the path and find the type at the end
          var reference = _this5.getReferenceForPropertyChain(rootType, currentProperty);
          // Add the plan step
          plan.addStepForProperty(reference.propertyTypeName, reference.service, currentProperty);
        }
      });

      return plan;
    }

    // Get the references for the specified type. If it is not found, an exception
    // is thrown.
  }, {
    key: 'getTypeReferences',
    value: function getTypeReferences(type) {
      var references = this._types[type];
      if (!references) {
        throw new Error('The type ' + type + ' is not registered');
      }
      return references;
    }
  }, {
    key: 'getPropertyReference',
    value: function getPropertyReference(references, property) {
      var reference = references[property];
      if (!reference) {
        throw new Error('The reference property ' + property + ' is not registered on ' + references._typeName);
      }

      // Ensure the service instance is injected
      if (!reference.service) {
        reference.service = this.$injector.get(reference.serviceName);
        if (!reference.service) {
          throw new Error('The reference property ' + property + ' on ' + references._typeName + '\n          requires service {$reference.serviceName} but it was not found');
        }
      }

      return reference;
    }

    // Returns the property reference, the definition for a single reference
    // property based on the type and the propertyChain, an array of properties
    // to traverse.
  }, {
    key: 'getReferenceForPropertyChain',
    value: function getReferenceForPropertyChain(type, property) {
      var _this6 = this;

      // Split the path by the '.' characters into an array
      var propertyChain = property.split('.');

      var propertyReference = null;
      propertyChain.forEach(function (p) {
        // Get the current type's references
        var typeReferences = _this6.getTypeReferences(type);
        // Get the reference for the next property on the current type
        propertyReference = _this6.getPropertyReference(typeReferences, p);
        // Use the reference to get the referenced key's type and service
        type = propertyReference.propertyTypeName;
      });
      return propertyReference;
    }
  }]);

  return ReferentialDataService;
})();

ReferentialDataService.$inject = ['$log', '$injector'];
exports['default'] = ReferentialDataService;<commit_msg>forgot to compile before last release..doh<commit_after>/* eslint-disable no-underscore-dangle */
import _ from 'lodash';
import Promise from 'bluebird';

export class Plan {

  constructor() {
    this.steps = [];
  }

  // Call this method to add a query to fetch and cache the data entities for
  // the referenced property. It may or may not create a plan step, depending
  // on whether an existing plan step can satisfy the retrieval.
  addStepForProperty(entityName, service, property) {

    // Split the path by the '.' characters into an array
    let propertyChain = property.split('.');

    // See if a plan step exists for this entity
    let planStep = _.find(this.steps, ['entity', entityName]);

    // If we didnt find one, create one and add it
    if (!planStep) {
      planStep = new PlanStep(entityName, service);
      this.steps.push(planStep);
    }

    // Add the property path if it doesn't exist
    if (planStep.paths.indexOf(property) === -1) {
      planStep.paths.push(property);
    }

    // Update the depth
    planStep.depth = Math.max(planStep.depth, propertyChain.length);

    // ensure they are sorted
    this.steps = _.sortBy(this.steps, ['depth', 'entity']);
  }

  findStepsForDepth(depth) {
    return _.filter(this.steps, step => step.depth === depth);
  }

  // Execute the query plan using the specified items.
  execute(items) {

    let planContext = {
      items: items,
      plan: this
    };

    // Build a chain of all the promises, starting with the plan context
    let promises = _.reduce(this.steps, (previous, step) => previous.bind(step).then(step.execute), Promise.resolve(planContext));
    return promises.bind();
  }
}

//  A PlanStep represents one step in the process to load and cache data. It
//  is created by the Plan.addStepForProperty method.
export class PlanStep {

  constructor(entity, service) {
    this.entity = entity; // The name of the entity, such as Account
    this.service = service; // A reference to the actual service
    this.paths = []; // An array of property reference paths
    //
    // paths: [
    //       'site.instructor',
    //       'site.grantee.administrator',
    //       'site.grantee.region.administrator'
    //     ],
    this.depth = 1; // The execution depth. This determines the order
    // in which the steps are executed. Plan steps
    // with the same value will be executed
    // concurrently.
  }

  // Returns a promise that performs the operations necessary to load the data
  // associated with this step. Be sure to bind the this argument to this step
  // instance. It assumes the incoming argument is a plan context and also
  // ensures the plan context is continued down the promise chain.
  execute(planContext) {

    let stepContext = {
      planContext: planContext,
      step: this
    };

    return Promise.resolve(stepContext).then(sc => {
      // Get the ids
      let ids = this.getPropertyValues(sc.planContext.items, sc.step);
      // Filter out any values that are already cached
      ids = sc.step.service.uncachedIds(ids);
      // Query and cache the entities
      return sc.step.service.loadByIds(ids);
    }).then(results => planContext);
  }

  //  Returns all the unique values for the properties specified in a query
  //  plan step's path argument. Items must be an array of items and step must
  //  be a valid step.
  //
  //   {
  //     entity: 'Account',
  //     service: AccountService,
  //     paths: [
  //       'site'.instructor',
  //       'site.grantee.administrator',
  //       'site.grantee.region.administrator'
  //     ],
  //     depth: 3
  //   }
  getPropertyValues(items, step) {

    // Initialize the id list that will be used to collect the values
    let ids = [];

    // For each item in the list, get the values
    items.forEach(item => {
      // Loop over the paths
      step.paths.forEach(path => {
        let pathChain = path.split('.');
        // Get all the id values
        this.collectPropertyValues(item, pathChain, ids);
      });
    });

    return _.uniq(ids);
  }

  collectPropertyValues(item, propertyChain, ids) {

    // Get the property we are extracting and update the property chain to
    // exclude the property we are currently processing.
    let property = propertyChain[0];
    propertyChain = propertyChain.slice(1); // Without the first element

    // If the propertyChain is empty, we know we are at the place we need to
    // collect the value.
    if (propertyChain.length === 0) {

      // Get the key from the lookup table
      let key = item._rdsReferences[property];
      // attempt to get the corresponding value
      let value = item[key];

      // Only process values that are defined
      if (value !== null && !_.isUndefined(value)) {
        // Add the value to the found ids
        if (Array.isArray(value)) {
          value.forEach(val => {
            ids.push(val);
          });
        } else {
          ids.push(value);
        }
      }
    } else {
      // Get the property value from the getter/setter
      let value = item[property];

      // Only process values that are defined
      if (value !== null && !_.isUndefined(value)) {
        // If we are not at the property yet, keep traversing
        if (Array.isArray(value)) {
          value.forEach(val => {
            this.collectPropertyValues(val, propertyChain, ids);
          });
        } else {
          this.collectPropertyValues(value, propertyChain, ids);
        }
      }
    }
  }

}

class ReferentialDataService {

  constructor($log, $injector) {

    // This types dictionary contains a key per type. Each key is a reference to
    // another dictionary. Here is an example:
    //
    // Class: {                                 <-- entityName
    //    _typeName: 'Class'                    <-- entityName
    //    'classType': {                        <-- propertyName
    //      propertyName: 'classType',          <-- key name without Id
    //      serviceName: 'classTypeService',    <-- name of the reference service
    //      service: classTypeService,          <-- Looked up after initialized
    //      keyName: 'classTypeId',
    //      propertyTypeName: 'ClassType',      <-- ref entityName
    //      property: {
    //        get: function() { ... },
    //        set: function(value) { ... }
    //      }
    //    }
    //  }

    this.$log = $log;
    this.$injector = $injector;
    this._types = {};
  }

  // Registers a foreign get reference. This information is used to create
  // getters and setters on model entities to reference related objects. It is
  // also used to load the referential data as well. To register a reference,
  // call this method with the following arguments
  //
  //  * typeName: the type name (class name)
  //  * keyName: the name of the property that holds the key reference(s)
  //      the key itself can be a single value or an array of values
  //  * serviceName: the related service name,
  //  * propertyTypeName: the referenced type.
  //  * propertyName: an optional name for the getter/setter defaults to
  //        the keyName minus the last two characters so it turns
  //        'propertyId' to 'property'.
  //
  //   NOTE: if your keyname is `propertyIds` you should provide a propertyName
  //     otherwise your property name will be `propertyI` when you
  //     probably want `properties`
  registerReference(typeName, keyName, serviceName, propertyTypeName, propertyName) {

    // Get the type definition or define it if not found
    let type = this._types[typeName];
    if (!type) {
      type = { _typeName: typeName };
      this._types[typeName] = type;
    }

    // Get the property name if not specifically assigned
    if (!propertyName) {
      propertyName = keyName.slice(0, -2);
    }

    // Get the reference definition or define it if not found
    let reference = type[propertyName];
    if (!reference) {
      reference = {};
      type[propertyName] = reference;
    }

    // Set the values of the reference
    reference.propertyName = propertyName;
    reference.keyName = keyName;
    reference.serviceName = serviceName;
    reference.propertyTypeName = propertyTypeName;

    // Create the getter and setter definition
    reference.property = {
      enumerable: false,
      get: function () {
        return reference.service.get(this[reference.keyName]);
      },
      set: function (value) {
        if (value) {
          this[reference.keyName] = value.key;
        } else {
          this[reference.keyName] = value;
        }
      }
    };
  }

  // In order to add the getter/setter methods for referential properties, this
  // method must be called on each model instance created. It adds the getter/
  // setter methods.
  configureClass(typeName, obj) {

    // Get the references for the type. If there are no references for the
    // type, just return.
    let type = this._types[typeName];
    if (!type) {
      return;
    }

    // Add the actual getter/setter properties to the class instance
    let referenceNames = Object.keys(type);
    //initialize a lookup table for use later.
    obj._rdsReferences = {};

    referenceNames.forEach(referenceName => {
      if (referenceName !== '_typeName') {
        let reference = type[referenceName];
        // Ensure the service instance is injected
        if (!reference.service) {
          reference.service = this.$injector.get(reference.serviceName);
          if (!reference.service) {
            throw new Error(`The reference property ${reference.propertyName} on ${typeName}
              requires service {$reference.serviceName} but it was not found`);
          }
        }
        Object.defineProperty(obj, reference.propertyName, reference.property);
        //create a lookup on the object for use later when executing the query plan
        obj._rdsReferences[reference.propertyName] = reference.keyName;
      }
    });
  }

  loadReferential(pagedItems, type, properties) {
    // Build the query plan
    let plan = this.buildQueryPlan(type, properties);

    // Return a promise for the execution of the query plan. Also,
    // return the original items if successful.
    return plan.execute(pagedItems.items).then(planContext => pagedItems);
  }

  // Fetch all the properties and sort them in ascenting order. This will ensure
  // child properties are processed after the parent. For example, site
  // would be processed before site.grantee and site.grantee.region
  // after that.
  //
  // An Array of Classes (ClassService)
  //   site (SiteService)
  //   site.instructor (AccountService)
  //   site.grantee.administrator (AccountService)
  //   site.grantee.region (RegionService)
  //   site.grantee.region.administrator (AccountService)
  //   classType (ClassTypeService)
  //   classRooms (ClassRoomService)
  //
  //
  // This should result in six queries, one for each of the entities: Class,
  // ClassRooms, Site, Region, ClassType, and Account. The should be executed
  // in the following order with the entities in [] executed in parallel:
  //
  //   Class (Already queried) -> [ClassRoom, Site, ClassType], Region, Account
  //
  // Loop over the tree and get the service types. For each type, determine the
  // maximum depth, the number of '.' in the path. Order the entities by depth.
  // An example of the data structure for the services in the example above is:
  //
  // {
  //   steps: [
  //     {
  //       entity: 'Site',
  //       serviceName: 'siteService',
  //       paths: [
  //          'site'
  //       ],
  //       depth: 1
  //     },
  //     {
  //       entity: 'ClassType',
  //       serviceName: 'classTypeService',
  //       paths: [
  //          'classType'
  //       ],
  //       depth: 1
  //     },
  //     {
  //       entity: 'ClassRoom',
  //       serviceName: 'classRoomService',
  //       paths: [
  //          'classRooms'
  //       ],
  //       depth: 1
  //     },
  //     {
  //       entity: 'Region',
  //       serviceName: 'regionService',
  //       paths: [
  //          'site.grantee.region'
  //       ],
  //       depth: 2
  //     },
  //     {
  //       entity: 'Account',
  //       serviceName: 'accountService',
  //       paths: [
  //         'site.instructor',
  //         'site.grantee.administrator',
  //         'site.grantee.region.administrator'
  //       ],
  //       depth: 3
  //     }
  //   ]
  // }
  //
  // To process them, makes sure they are sorted in order by depth. Loop over
  // the depths and process the entities at each depth in parallel.
  //
  //  for each depth d {
  //    for each entity e at depth {
  //      traverse the paths for the entity and collect all the ids
  //      get the distinct list of ids
  //      check each id and remove it if it is already cached
  //      make the request with a then that caches the results and return the
  //        promise
  //    }
  //  }

  // Takes in an array of entities to query and builds the query plan. An
  // example input could be:
  //
  //   site (SiteService)
  //   site.instructor (AccountService)
  //   site.grantee.administrator (AccountService)
  //   site.grantee.region (RegionService)
  //   site.grantee.region.administrator (AccountService)
  //   classType (ClassTypeService)
  //   classRooms (ClassRoomService)
  //
  // It returns a plan that can be used to execute
  buildQueryPlan(rootType, propertyList) {

    let plan = new Plan();

    propertyList.forEach(property => {

      // Add the property and all sub properties. For example, you add
      // site.grantee.region, be sure site and site.grantee are
      // in the list.
      let propertyChain = property.split('.');
      for (let i = 0; i < propertyChain.length; i++) {
        let currentProperty = propertyChain.slice(0, i + 1).join('.');
        // Follow the path and find the type at the end
        let reference = this.getReferenceForPropertyChain(rootType, currentProperty);
        // Add the plan step
        plan.addStepForProperty(reference.propertyTypeName, reference.service, currentProperty);
      }
    });

    return plan;
  }

  // Get the references for the specified type. If it is not found, an exception
  // is thrown.
  getTypeReferences(type) {
    let references = this._types[type];
    if (!references) {
      throw new Error('The type ' + type + ' is not registered');
    }
    return references;
  }

  getPropertyReference(references, property) {
    let reference = references[property];
    if (!reference) {
      throw new Error(`The reference property ${property} is not registered on ${references._typeName}`);
    }

    // Ensure the service instance is injected
    if (!reference.service) {
      reference.service = this.$injector.get(reference.serviceName);
      if (!reference.service) {
        throw new Error(`The reference property ${property} on ${references._typeName}
          requires service {$reference.serviceName} but it was not found`);
      }
    }

    return reference;
  }

  // Returns the property reference, the definition for a single reference
  // property based on the type and the propertyChain, an array of properties
  // to traverse.
  getReferenceForPropertyChain(type, property) {

    // Split the path by the '.' characters into an array
    let propertyChain = property.split('.');

    let propertyReference = null;
    propertyChain.forEach(p => {
      // Get the current type's references
      let typeReferences = this.getTypeReferences(type);
      // Get the reference for the next property on the current type
      propertyReference = this.getPropertyReference(typeReferences, p);
      // Use the reference to get the referenced key's type and service
      type = propertyReference.propertyTypeName;
    });
    return propertyReference;
  }

}

ReferentialDataService.$inject = ['$log', '$injector'];
export default ReferentialDataService;<|endoftext|>


<commit_before>var PREVIOUS_CARD_INDEX = "previousCardIndex";
var CARD_EVENT_ALLOWED = "cardEventAllowed";

/**
 * Returns a random int value between min and max
 * values (included)
 * http://stackoverflow.com/a/7228322
 *
 * @param  {Int} min Minimum number
 * @param  {Int} max Maximum number
 * @return {Int}     A random number between given values
 */
function randomIntFromInterval(min, max) {
	return Math.floor(Math.random() * (max - min + 1) + min);
}

/**
 * Fill and returns an array with every possible index for memory game.
 * Using the length value passed as param, every value between 0 and length
 * is appended twice to array
 * @param  {Int} length Max value to use
 * @return {[Int]}        Filled array
 */
function fillPossibleIndexesWithLenght(length) {
	// Empty array which will contains the possible
	// image indexes
	var possible_indexes = [];
	// Memory game needs every value appears twice.
	// The half_length permits to use only length/2 values
	var half_length = length / 2;
	// Iterate every card
	for (var i = 0; i < length; i++) {
		var j = i;
		// If the index is greater than half_length, it
		// must restart from 0
		if (j >= half_length) {
			j -= half_length;
		}
		possible_indexes[i] = j;
	}

	return possible_indexes;
}

/**
 * Create a new memory cards array using the possible indexes.
 * Every card is filled with a random element from possible_indexes array
 *
 * @param  {[Int]} possible_indexes Array containing the possible indexes for Cards
 * @return {[]}                  Array with created card for memory game
 */
function createMemoryCardsArrayWithIndexes(possible_indexes) {
	var cards = [];
	// Save the initial possible_indexes length
	var length = possible_indexes.length;
	for (var i = 0; i < length; i++) {
		// Gets a random index from possible_indexes array
		var randomIdx = randomIntFromInterval(0, possible_indexes.length - 1);
		// New card
		cards[i] = {
			image_index: possible_indexes[randomIdx],
			removed: false
		};
		// Remove used index from possible_indexes
		possible_indexes.splice(randomIdx, 1);
	}

	return cards;
}

// When template is created, the array is initialized
Template.memory.onCreated(function memoryOnCreated() {
	Session.set(PREVIOUS_CARD_INDEX, undefined);
	Session.set(CARD_EVENT_ALLOWED, true);

	var length = 16;
	if (length % 2 != 0) {
		console.error("Not even value for memory game.");
		return;
	}
	var possible_indexes = fillPossibleIndexesWithLenght(length);

	var cards = createMemoryCardsArrayWithIndexes(possible_indexes);
	// Save cards array in Session, so changing it, the getArray()
	// function in helpers will be recalled
	Session.set('cardsArray', cards);
});

// Helpers for memory template
Template.memory.helpers({
	/**
	 * Gets the cards array saved in Session.
	 * It is called every time a new object with key 'cardsArray'
	 * is saved in Session
	 * @return {Array} Cards array
	 */
	getArray() {
		var cards = Session.get('cardsArray');

		// Check remaining cards for Win check
		var remainingCards = 0;
		for (var i = 0; i < cards.length; i++) {
			if(cards[i].removed == false){
				remainingCards++;
				break;
			}
		}
		if(remainingCards == 0){
			Blaze._globalHelpers.showToast("You won!");
		}
		return cards;
	}
});

// Events for memory template
Template.memory.events({
	'click .card-image' (event, t) {
		// If another card event is running, this one is prevented
		if (!Session.get(CARD_EVENT_ALLOWED)) {
			return;
		}
		// Block other click events
		Session.set(CARD_EVENT_ALLOWED, false);
		var ANIMATION_SPEED_PLACEHOLDER = "fast";
		var ANIMATION_SPEED_CARD = "slow";
		// Get index from selected item
		var index = $(event.target).data("value");
		// Gets the previously saved (if exists) index of selected card
		var previous_selected_index = Session.get(PREVIOUS_CARD_INDEX);

		// Unblock other events
		if (previous_selected_index == index) {
			Session.set(CARD_EVENT_ALLOWED, true);
			return;
		}
		console.log("Flipped: " + index);
		console.log("Previously flipped: " + previous_selected_index);
		// Fade in/out placeholder
		$(".placeholder-card.card-image-" + index).fadeToggle(ANIMATION_SPEED_PLACEHOLDER, function() {
			// Fade in/out image
			$(".real-card.card-image-" + index).fadeToggle(ANIMATION_SPEED_CARD, function() {
				// If no index is found, save the currently selected index
				if (previous_selected_index == undefined) {
					console.log("No previous index");
					Session.set(PREVIOUS_CARD_INDEX, index);
					Session.set(CARD_EVENT_ALLOWED, true);
					return;
				}

				// Gets pair of cards
				var cards = Session.get('cardsArray');
				var previousCard = cards[previous_selected_index];
				var currentCard = cards[index];

				var foundPair = previousCard.image_index == currentCard.image_index;
				if (foundPair) {
					console.log("Found Pair");
					// Remove found cards
					cards[previous_selected_index].removed = true;
					cards[index].removed = true;
					Session.set('cardsArray', cards);
					Session.set(PREVIOUS_CARD_INDEX, undefined);
				} else {
					// Clear saved index
					Session.set(PREVIOUS_CARD_INDEX, undefined);
					// Reset cards
					$(".card-image-" + previous_selected_index).fadeToggle(ANIMATION_SPEED_PLACEHOLDER);
					$(".card-image-" + index).fadeToggle(ANIMATION_SPEED_PLACEHOLDER);
				}
				Session.set(CARD_EVENT_ALLOWED, true);
			});

		});
	},
});<commit_msg>Comments<commit_after>var PREVIOUS_CARD_INDEX = "previousCardIndex";
var CARD_EVENT_ALLOWED = "cardEventAllowed";

/**
 * Returns a random int value between min and max
 * values (included)
 * http://stackoverflow.com/a/7228322
 *
 * @param  {Int} min Minimum number
 * @param  {Int} max Maximum number
 * @return {Int}     A random number between given values
 */
function randomIntFromInterval(min, max) {
	return Math.floor(Math.random() * (max - min + 1) + min);
}

/**
 * Fill and returns an array with every possible index for memory game.
 * Using the length value passed as param, every value between 0 and length
 * is appended twice to array
 * @param  {Int} length Max value to use
 * @return {[Int]}        Filled array
 */
function fillPossibleIndexesWithLenght(length) {
	// Empty array which will contains the possible
	// image indexes
	var possible_indexes = [];
	// Memory game needs every value appears twice.
	// The half_length permits to use only length/2 values
	var half_length = length / 2;
	// Iterate every card
	for (var i = 0; i < length; i++) {
		var j = i;
		// If the index is greater than half_length, it
		// must restart from 0
		if (j >= half_length) {
			j -= half_length;
		}
		possible_indexes[i] = j;
	}

	return possible_indexes;
}

/**
 * Create a new memory cards array using the possible indexes.
 * Every card is filled with a random element from possible_indexes array
 *
 * @param  {[Int]} possible_indexes Array containing the possible indexes for Cards
 * @return {[]}                  Array with created card for memory game
 */
function createMemoryCardsArrayWithIndexes(possible_indexes) {
	var cards = [];
	// Save the initial possible_indexes length
	var length = possible_indexes.length;
	for (var i = 0; i < length; i++) {
		// Gets a random index from possible_indexes array
		var randomIdx = randomIntFromInterval(0, possible_indexes.length - 1);
		// New card
		cards[i] = {
			image_index: possible_indexes[randomIdx],
			removed: false
		};
		// Remove used index from possible_indexes
		possible_indexes.splice(randomIdx, 1);
	}

	return cards;
}

// When template is created, the array is initialized
Template.memory.onCreated(function memoryOnCreated() {
	// Setup move counter
	this.counter = new ReactiveVar(0);

	// Initial setup for card events
	Session.set(PREVIOUS_CARD_INDEX, undefined);
	Session.set(CARD_EVENT_ALLOWED, true);

	var length = 16;
	if (length % 2 != 0) {
		console.error("Not even value for memory game.");
		return;
	}
	var possible_indexes = fillPossibleIndexesWithLenght(length);

	var cards = createMemoryCardsArrayWithIndexes(possible_indexes);
	// Save cards array in Session, so changing it, the getArray()
	// function in helpers will be recalled
	Session.set('cardsArray', cards);
});

// Helpers for memory template
Template.memory.helpers({
	/**
	 * Gets move counter
	 *
	 * @return {Int} Move counter
	 */
	counter() {
		return Template.instance().counter.get();
	},
	/**
	 * Gets the cards array saved in Session.
	 * It is called every time a new object with key 'cardsArray'
	 * is saved in Session
	 * @return {Array} Cards array
	 */
	getArray() {
		var cards = Session.get('cardsArray');

		// Check remaining cards for Win check
		var remainingCards = 0;
		for (var i = 0; i < cards.length; i++) {
			if (cards[i].removed == false) {
				remainingCards++;
				break;
			}
		}
		if (remainingCards == 0) {
			Blaze._globalHelpers.showToast("You won!");
		}
		return cards;
	}
});

// Events for memory template
Template.memory.events({
	// TODO: Refactor function
	'click .card-image' (event, instance) {
		// If another card event is running, this one is prevented
		if (!Session.get(CARD_EVENT_ALLOWED)) {
			return;
		}
		// Block other click events
		Session.set(CARD_EVENT_ALLOWED, false);
		var ANIMATION_SPEED_PLACEHOLDER = "fast";
		var ANIMATION_SPEED_CARD = "slow";
		// Get index from selected item
		var index = $(event.target).data("value");
		// Gets the previously saved (if exists) index of selected card
		var previous_selected_index = Session.get(PREVIOUS_CARD_INDEX);

		// Unblock other events
		if (previous_selected_index == index) {
			Session.set(CARD_EVENT_ALLOWED, true);
			return;
		}

		// Increment move counter
		instance.counter.set(instance.counter.get() + 1);

		console.log("Flipped: " + index);
		console.log("Previously flipped: " + previous_selected_index);
		// Fade in/out placeholder
		$(".placeholder-card.card-image-" + index).fadeToggle(ANIMATION_SPEED_PLACEHOLDER, function() {
			// Fade in/out image
			$(".real-card.card-image-" + index).fadeToggle(ANIMATION_SPEED_CARD, function() {
				// If no index is found, save the currently selected index
				if (previous_selected_index == undefined) {
					console.log("No previous index");
					Session.set(PREVIOUS_CARD_INDEX, index);
					Session.set(CARD_EVENT_ALLOWED, true);
					return;
				}

				// Gets pair of cards
				var cards = Session.get('cardsArray');
				var previousCard = cards[previous_selected_index];
				var currentCard = cards[index];

				var foundPair = previousCard.image_index == currentCard.image_index;
				if (foundPair) {
					console.log("Found Pair");
					// Remove found cards
					cards[previous_selected_index].removed = true;
					cards[index].removed = true;
					Session.set('cardsArray', cards);
					Session.set(PREVIOUS_CARD_INDEX, undefined);
				} else {
					// Clear saved index
					Session.set(PREVIOUS_CARD_INDEX, undefined);
					// Reset cards
					$(".card-image-" + previous_selected_index).fadeToggle(ANIMATION_SPEED_PLACEHOLDER);
					$(".card-image-" + index).fadeToggle(ANIMATION_SPEED_PLACEHOLDER);
				}
				Session.set(CARD_EVENT_ALLOWED, true);
			});

		});
	},
});<|endoftext|>


<commit_before>"use strict";

/* The state structure:
{
	tab: 'act',
  	runs: {
      	isFetching: true,
      	didInvalidate: false,
	  	lastUpdated: 1439478405547,
		filter: { key: 'yr:2017', count: 145 },
      	items: [
        	{ id: 1, date: "2016-12-31", year: 2016, ... },
			{ id: 2, date: "2016-12-22", year: 2016, ... }
			...
      	],
		grouped: {
			week: {
				2017w15: { count: 5, distance: 35, duration: '4:50:49', kcal: 7023, elevation: 957, maxDistance: 12.3 },
	 			...
			},
			month: {
				2017m1: { count: 14, distance: 95, duration: '16:50:49', kcal: 22023, elevation: 5357, maxDistance: 15.2 },
	 			...
			},
			year: {
				2016: { count: 145, distance: 1067, duration: "108:20:10", elevation: 15886, kcal: 96988, maxDistance: 21.1 },
	 			...
			},
			all: {
				total: { count: 445, distance: 3067, duration: "408:20:10", elevation: 35886, kcal: 426988, maxDistance: 21.1 }
			},
		},
		stats: 'week',
		isImporting: false,
		lastImported: 1239478406530,
		newActivities: 0,
		showImportModal: false
	}
	stats: {
		total: {  // obsolete
      		isFetching: true,
	  		lastUpdated: 1439478405547,
			data: {count: 411, distance: 2673.85, ...}
		}
	}
}
*/

var combineReducers = require('redux').combineReducers;
var tab = require('./tab').reducer;
var runs = require('./runs').reducer;
//var stats = require('./stats').reducer;

const reducer = combineReducers({
	tab,
  	runs
})

module.exports.reducer = reducer;
<commit_msg>One more fix of sorting<commit_after>"use strict";

/* The state structure:
{
	tab: 'act',
  	runs: {
      	isFetching: true,
      	didInvalidate: false,
	  	lastUpdated: 1439478405547,
		filter: { key: 'yr:2017', count: 145 },
      	items: [
        	{ id: 1, date: "2016-12-31", year: 2016, ... },
			{ id: 2, date: "2016-12-22", year: 2016, ... }
			...
      	],
		grouped: {
			week: {
				2017w15: { count: 5, distance: 35, duration: '4:50:49', kcal: 7023, elevation: 957, maxDistance: 12.3 },
	 			...
			},
			month: {
				2017m01: { count: 14, distance: 95, duration: '16:50:49', kcal: 22023, elevation: 5357, maxDistance: 15.2 },
	 			...
			},
			year: {
				2016: { count: 145, distance: 1067, duration: "108:20:10", elevation: 15886, kcal: 96988, maxDistance: 21.1 },
	 			...
			},
			all: {
				total: { count: 445, distance: 3067, duration: "408:20:10", elevation: 35886, kcal: 426988, maxDistance: 21.1 }
			},
		},
		stats: 'week',
		isImporting: false,
		lastImported: 1239478406530,
		newActivities: 0,
		showImportModal: false
	}
	stats: {
		total: {  // obsolete
      		isFetching: true,
	  		lastUpdated: 1439478405547,
			data: {count: 411, distance: 2673.85, ...}
		}
	}
}
*/

var combineReducers = require('redux').combineReducers;
var tab = require('./tab').reducer;
var runs = require('./runs').reducer;
//var stats = require('./stats').reducer;

const reducer = combineReducers({
	tab,
  	runs
})

module.exports.reducer = reducer;
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import PhodomeScene from './components/PhodomeScene';

// The only purpose of this target variable is so that when running the client
// tests inside Chrome with Karma, where OUR index.html is not loaded, React
// won't try to render to a nonexistent DOM element .scene-container and will
// instead just render on the document body (which is guaranteed to exist on
// any given page, I think)
let target = document.querySelector('.scene-container');

if (!target) {
  target = document.querySelector('body');
}

ReactDOM.render(<PhodomeScene/>, target);
<commit_msg>(cleanup) Remove unnecessary ReactDOM conditional logic<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import PhodomeScene from './components/PhodomeScene';

ReactDOM.render(<PhodomeScene />, document.querySelector('.scene-container'));
<|endoftext|>


<commit_before>/*
Name: mag-komposer v0.2.2
Description: side loading props based on react-komposer (https://github.com/kadirahq/react-komposer)
Let's compose MagJS containers and feed data into components.
Author: Michael Glazer
License: MIT
Homepage: https://github.com/magnumjs/mag.js
@requires mag.js(v0.25.7) & mag addons 0.22.5
(c) 2017
Simple Example: http://embed.plnkr.co/RkCuy0xoyUZyMjSPRKeH/
Full Example: http://embed.plnkr.co/LoI7vOx1KAZkaA8dZSJX/
*/

mag.komposer = function(handlerFunc, loadingComp, errorComp) {
  return function(composed) {
    var lnode = document.createElement('div');
    lnode.appendChild(lnode.cloneNode());

    loadingComp = loadingComp || mag.create(lnode, {
      view: function(state) {
        state.div = 'Loading...';
      }
    });

    //Default error component can be over ridden:
    var enode = document.createElement('div');
    enode.appendChild(enode.cloneNode());

    errorComp = errorComp || mag.create(enode, {
      view: function(state, props) {
        state.div = {
          _text: props.message,
          _style: 'color:red',
        };
      }
    });

    var cleaners = [],
      errors = [],
      loading = [];
    var _subscribe = function(props, instanceID) {
      //why is this here, onunload?
      if (cleaners[props.key]) cleaners[props.key]();

      cleaners[props.key] = handlerFunc(props, function(key, ids, nprops) {

        loading[key] = false;
        var cp = composed().getProps(ids);

        if (nprops instanceof Error) {
          errors[key] = true;
          error = {};
          Object.getOwnPropertyNames(nprops).forEach(function(skey) {
            error[skey] = nprops[skey];
          });
          mag.merge(cp, error);

        } else {
          mag.merge(cp, nprops);
        }

        var rnode = mag.getNode(mag.getId(ids));
        if (rnode) {
          mag.redraw(rnode, ids);
        }

      }.bind({}, props.key, instanceID));
    };

    var prevProps = [];
    var hoc = {
      controller: function(props) {
        if (!props.key) props.key = performance.now();
        loading[props.key] = true;

        this.willgetprops = function(node, cprops, instanceId) {
          _subscribe(cprops, instanceId)
        };
        this.willupdate = function(node, newProps) {
          // prevent layout thrashing on init when no props
          //TODO: real use case?

          //don't rerun when unecessary
          if (newProps.key && JSON.stringify(prevProps[newProps.key]) === JSON.stringify(newProps)) {
            return false;
          } else {
            prevProps[newProps.key] = mag.copy(newProps);
          }
        }
      },
      view: function(state, props) {
        //is loading?
        if (loading[props.key]) {
          state.div = loadingComp(props);
          return;
        }
        //is error?
        if (errors[props.key]) {
          state.div = errorComp(props);
          return;
        }
        state.div = composed(props);
      }
    };

    // virtual node
    var node = document.createElement('div');
    node.appendChild(node.cloneNode())

    return mag.create(node, hoc);
  }
};
<commit_msg>Update mag-komposer.js<commit_after>/*
Name: mag-komposer v0.2.3
Description: side loading props based on react-komposer (https://github.com/kadirahq/react-komposer)
Let's compose MagJS containers and feed data into components.
Author: Michael Glazer
License: MIT
Homepage: https://github.com/magnumjs/mag.js
@requires mag.js(v0.25.7) & mag addons 0.22.5
(c) 2017
Simple Example: http://embed.plnkr.co/RkCuy0xoyUZyMjSPRKeH/
Full Example: http://embed.plnkr.co/LoI7vOx1KAZkaA8dZSJX/
*/

mag.komposer = function(handlerFunc, loadingComp, errorComp) {
  return function(composed) {
    var lnode = document.createElement('div');
    lnode.appendChild(lnode.cloneNode());

    loadingComp = loadingComp || mag.create(lnode, {
      view: function(state) {
        state.div = 'Loading...';
      }
    });

    //Default error component can be over ridden:
    var enode = document.createElement('div');
    enode.appendChild(enode.cloneNode());

    errorComp = errorComp || mag.create(enode, {
      view: function(state, props) {
        state.div = {
          _text: props.message,
          _style: 'color:red',
        };
      }
    });

    var cleaners = [],
      errors = [],
      loading = [];
    var _subscribe = function(props, instanceID) {
      //why is this here, onunload?
      if (cleaners[props.key]) cleaners[props.key]();

      cleaners[props.key] = handlerFunc(props, function(key, ids, nprops) {

        loading[key] = false;
        var cp = mag.mod.getProps(ids);

        if (nprops instanceof Error) {
          errors[key] = true;
          error = {};
          Object.getOwnPropertyNames(nprops).forEach(function(skey) {
            error[skey] = nprops[skey];
          });
          mag.mod.setProps(ids, mag.merge(cp, error))

        } else {
          mag.mod.setProps(ids, mag.merge(cp, nprops))
        }

        var rnode = mag.getNode(mag.getId(ids));
        if (rnode) {
          mag.redraw(rnode, ids);
        }

      }.bind({}, props.key, instanceID));
    };

    var prevProps = [];
    var hoc = {
      controller: function(props) {
        if (!props.key) props.key = performance.now();
        loading[props.key] = true;

        this.didload = function(node, cprops, instanceId) {
          _subscribe(cprops, instanceId);
        };

        this.willgetprops = function(node, cprops, instanceId) {
          _subscribe(cprops, instanceId)
        };
      },
      view: function(state, props) {
        //is loading?
        if (loading[props.key]) {
          state.div = loadingComp(props);
          return;
        }
        //is error?
        if (errors[props.key]) {
          state.div = errorComp(props);
          return;
        }
        state.div = composed(props);
      }
    };

    // virtual node
    var node = document.createElement('div');
    node.appendChild(node.cloneNode())

    return mag.create(node, hoc);
  }
};
<|endoftext|>


<commit_before><commit_msg>Update stations-america.js<commit_after><|endoftext|>


<commit_before>// This file is part of the Shapy project.
// Licensing information can be found in the LICENSE file.
// (C) 2015 The Shapy Team. All rights reserved.
goog.provide('shapy.browser.Service');

goog.require('shapy.browser.Asset');
goog.require('shapy.browser.Asset.Dir');
goog.require('shapy.browser.Asset.Scene');
goog.require('shapy.browser.Asset.Texture');

/**
 * Service that handles asset browsing.
 *
 *
 * @constructor
 * @ngInject
 *
 * @param {!angular.$http}           $http   The angular http service.
 * @param {!angular.$q}              $q      The angular promise service.
 * @param {!shapy.modal.Service}     shModal The modal service.
 */
shapy.browser.Service = function($http, $q, shModal) {
  /** @private {!angular.$http} @const */
  this.http_ = $http;
  /** @private {!angular.$q} @const */
  this.q_ = $q;

  /*
   * Cached scenes.
   * @private {!Object<string, shapy.browser.Asset.Scene>} @const
   */
  this.scenes_ = {};

  /**
   * Cached directories.
   * @private {!Object<string, shapy.browser.Asset.Dir>} @const
   */
  this.dirs_ = {};

  /**
   * Cached textures.
   * @private {!Object<string, shapy.browser.Asset.Texture>} @const
   */
  this.textures_ = {};

  /**
   * Private home dir.
   *
   * @public {shapy.browser.Asset.Dir}
   * @const
   */
  this.home = null;

  /**
   * Current directory.
   * @public {shapy.browser.Asset.Dir}
   */
  this.current = null;

  /**
   * Type of current directory.
   * @type {boolean}
   * @public
   * @export
   */
  this.private = true;

  /**
   * Path to current folder.
   * @public {Array.<shapy.browser.Asset.Dir>}
   * @export
   */
  this.path = [];

  /**
   * Query from user filtering assets.
   * @public {string}
   * @export
   */
  this.query = '';
};

/**
 * Updates data regarding current directory.
 *
 * @param {!shapy.browser.Asset.Dir} dir Dir entered.
 */
shapy.browser.Service.prototype.changeDirectory = function(dir) {
  // Update current dir and its type.
  this.current = dir;
  this.private = dir.id >= 0;

  // If entered private dir, update path.
  if (this.private) {
    // Compose new path to current dir.
    var newPath = [dir];
    var current = dir;
    while (current.parent !== null) {
      current = current.parent;
      newPath.push(current);
    }
    newPath.reverse();

    this.path = newPath;
  }
};

/**
 * Returns default name for assets
 *
 * @param {shapy.browser.Asset.Type} type Type of asset for which we return name
 */
shapy.browser.Service.prototype.defaultName = function(type) {
  switch (type) {
    case shapy.browser.Asset.Type.DIRECTORY: return 'Untitled Folder';
    case shapy.browser.Asset.Type.SCENE:     return 'Untitled Scene';
    case shapy.browser.Asset.Type.TEXTURE:   return 'Untitled Texture';
    default:                                 return 'Untitled';
  }
};

/**
 * Creates a new asset.
 *
 * @private
 *
 * @param {string}   url  URL of the resource.
 * @param {!Object<string, Object>} cache Cache for the resource.
 * @param {Function} cons Asset constructor.
 *
 * @return {!angular.$q} Promise to return a new asset.
 */
shapy.browser.Service.prototype.create_ = function(url, cache, cons) {
  var parent = this.current;
  // Request new asset from server.
  return this.http_.post(url, { parent: parent.id })
      .then(goog.bind(function(response) {
        // Create asset representation, cache it, update parent/child refs.
        var asset = new cons(this, response.data.id, response.data);
        cache[asset.id] = asset;
        parent.children.push(asset);
        asset.parent = parent;
      }, this));
};

/**
 * Creates new dir.
 *
 * @return {!angular.$q} Promise to return a new dir.
 */
shapy.browser.Service.prototype.createDir = function() {
  return this.create_('/api/assets/dir', this.dirs_, shapy.browser.Asset.Dir);
};

/**
 * Creates a new scene.
 *
 * @return {!angular.$q} Promise to return a new scene.
 */
shapy.browser.Service.prototype.createScene = function() {
  return this.create_(
      '/api/assets/scene',
      this.scenes_,
      shapy.browser.Asset.Scene);
};

/**
 * Creates a new texture.
 *
 * @return {!angular.$q} Promise to return a new texture.
 */
shapy.browser.Service.prototype.createTexture = function() {
  return this.create_(
      '/api/assets/texture',
      this.textures_,
      shapy.browser.Asset.Texture);
};

/**
 * Fetches asset.
 *
 * @private
 *
 * @param {string}                  url   URL of the resource.
 * @param {!Object<string, Object>} cache Cache for the resource.
 * @param {Function}                cons  Asset constructor.
 * @param {string}                  id    ID of the resource.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.get_ = function(url, cache, cons, id) {
  if (!goog.isDef(id)) {
    return this.q_.reject({ error: 'Invalid ID.' });
  }

  var asset;
  // Check cache.
  if (goog.object.containsKey(cache, id)) {
    asset = cache[id];
    // Return if asset loaded.
    if (asset.ready) {
      return asset.ready.promise;
    }
  } else {
    // Construct (unloaded) asset.
    asset = new cons(this, id);
    cache[id] = asset;
  }

  // Request asset data from server.
  asset.ready = this.q_.defer();
  this.http_.get(url, {params: { id: id }})
    .then(goog.bind(function(response) {
      asset.load(response.data);
      asset.ready.resolve(asset);
    }, this), function() {
      asset.ready.reject();
    });

  return asset.ready.promise;
};

/**
 * Fetches a dir from the server or from local storage.
 *
 * @param {number} dirID ID of the directory.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.getDir = function(dirID) {
  return this.get_(
      '/api/assets/dir',
      this.dirs_,
      shapy.browser.Asset.Dir,
      dirID
  );
};

/**
 * Fetches a scene from the server or from local storage.
 *
 * @param {number} sceneID ID of the scene.
 *
 * @return {!angular.$q} Promise to return the scene.
 */
shapy.browser.Service.prototype.getScene = function(sceneID) {
  return this.get_(
      '/api/assets/scene',
      this.scenes_,
      shapy.browser.Asset.Scene,
      sceneID
  );
};

/**
 * Fetches a texture from the server or from local storage.
 *
 * @param {number} textureID ID of the scene.
 *
 * @return {!angular.$q} Promise to return the scene.
 */
shapy.browser.Service.prototype.getTexture = function(textureID) {
  return this.get_(
      '/api/assets/textures',
      this.textures_,
      shapy.browser.Asset.Texture,
      textureID
  );
};

/**
 * Fetches public space from the server or from local storage.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.getPublic = function() {
  return this.get_(
      '/api/assets/public',
      this.dirs_,
      shapy.browser.Asset.Dir,
      shapy.browser.Asset.Space.PUBLIC
  );
};

/**
 * Fetches shared space from the server or from local storage.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.getShared = function() {
  return this.get_(
      '/api/assets/shared',
      this.dirs_,
      shapy.browser.Asset.Dir,
      shapy.browser.Asset.Space.SHARED
  );
};

/**
 * Fetches filtered space from the server or from local storage.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.getFiltered = function(id) {
  return this.get_(
      '/api/assets/filtered',
      this.dirs_,
      shapy.browser.Asset.Dir,
      id
  );
};

/**
 * Renames asset.
 *
 * @param {string}               url   URL of the resource.
 * @param {!shapy.browser.Asset} asset Asset to rename.
 * @param {string}               name  New name.
 */
shapy.browser.Service.prototype.rename_ = function(url, asset, name) {
  asset.name = name;
  this.http_.put(url, {
    id: asset.id,
    name: name
  });
};

/**
 * Renames dir.
 *
 * @param {!shapy.browser.Asset.Dir} dir  Dir to rename.
 * @param {string}                   name New name.
 */
shapy.browser.Service.prototype.renameDir = function(dir, name) {
  this.rename_('/api/assets/dir', dir, name);
};

/**
 * Renames scene.
 *
 * @param {!shapy.browser.Asset.Scene} scene Scene to rename.
 * @param {string}                     name  New name.
 */
shapy.browser.Service.prototype.renameScene = function(scene, name) {
  this.rename_('/api/assets/scene', scene, name);
};

/**
 * Deletes asset.
 *
 * @param {string}                  url   URL of the resource.
 * @param {!shapy.browser.Asset}    asset Asset to rename.
 * @param {!Object<string, Object>} cache Cache for the resource.
 */
shapy.browser.Service.prototype.delete_ = function(url, asset, cache) {
  // Early return for faulty deletes
  if (asset.id <= 0)
    return;

  // Delete on server
  this.http_.delete(url, {params: {id: asset.id}}).then(goog.bind(function() {
    // Update parent
    this.current.children = this.current.children.filter(function(child) {
      return asset.id !== child.id;
    });
    // Clean cache
    this.removefromCache(asset);
  }, this));
};

/**
 * Recursively removes assets from caches
 *
 * @param {!shapy.browser.Asset}    asset Asset which (with children) we remove.
 */
shapy.browser.Service.prototype.removefromCache = function(asset) {
  switch (asset.type) {
    case shapy.browser.Asset.Type.DIRECTORY:
      goog.object.remove(asset.shBrowser_.dirs_, asset.id);
      break;
    case shapy.browser.Asset.Type.SCENE:
      goog.object.remove(asset.shBrowser_.scenes_, asset.id);
      break;
    case shapy.browser.Asset.Type.TEXTURE:
      goog.object.remove(asset.shBrowser_.textures_, asset.id);
      break;
  }
  if (asset.children) {
    goog.array.forEach(asset.children, asset.shBrowser_.removefromCache);
  }
};

/**
 * Deletes dir.
 *
 * @param {!shapy.browser.Asset.Dir} dir  Dir to delete.
 */
shapy.browser.Service.prototype.deleteDir = function(dir) {
  this.delete_('/api/assets/dir', dir, this.dirs_);
};

/**
 * Deletes scene.
 *
 * @param {!shapy.browser.Asset.Scene} scene Scene to delete.
 */
shapy.browser.Service.prototype.deleteScene = function(scene) {
  this.delete_('/api/assets/scene', scene, this.scenes_);
};

<commit_msg>Rename only if backend operation succeeded<commit_after>// This file is part of the Shapy project.
// Licensing information can be found in the LICENSE file.
// (C) 2015 The Shapy Team. All rights reserved.
goog.provide('shapy.browser.Service');

goog.require('shapy.browser.Asset');
goog.require('shapy.browser.Asset.Dir');
goog.require('shapy.browser.Asset.Scene');
goog.require('shapy.browser.Asset.Texture');

/**
 * Service that handles asset browsing.
 *
 *
 * @constructor
 * @ngInject
 *
 * @param {!angular.$http}           $http   The angular http service.
 * @param {!angular.$q}              $q      The angular promise service.
 * @param {!shapy.modal.Service}     shModal The modal service.
 */
shapy.browser.Service = function($http, $q, shModal) {
  /** @private {!angular.$http} @const */
  this.http_ = $http;
  /** @private {!angular.$q} @const */
  this.q_ = $q;

  /*
   * Cached scenes.
   * @private {!Object<string, shapy.browser.Asset.Scene>} @const
   */
  this.scenes_ = {};

  /**
   * Cached directories.
   * @private {!Object<string, shapy.browser.Asset.Dir>} @const
   */
  this.dirs_ = {};

  /**
   * Cached textures.
   * @private {!Object<string, shapy.browser.Asset.Texture>} @const
   */
  this.textures_ = {};

  /**
   * Private home dir.
   *
   * @public {shapy.browser.Asset.Dir}
   * @const
   */
  this.home = null;

  /**
   * Current directory.
   * @public {shapy.browser.Asset.Dir}
   */
  this.current = null;

  /**
   * Type of current directory.
   * @type {boolean}
   * @public
   * @export
   */
  this.private = true;

  /**
   * Path to current folder.
   * @public {Array.<shapy.browser.Asset.Dir>}
   * @export
   */
  this.path = [];

  /**
   * Query from user filtering assets.
   * @public {string}
   * @export
   */
  this.query = '';
};

/**
 * Updates data regarding current directory.
 *
 * @param {!shapy.browser.Asset.Dir} dir Dir entered.
 */
shapy.browser.Service.prototype.changeDirectory = function(dir) {
  // Update current dir and its type.
  this.current = dir;
  this.private = dir.id >= 0;

  // If entered private dir, update path.
  if (this.private) {
    // Compose new path to current dir.
    var newPath = [dir];
    var current = dir;
    while (current.parent !== null) {
      current = current.parent;
      newPath.push(current);
    }
    newPath.reverse();

    this.path = newPath;
  }
};

/**
 * Returns default name for assets
 *
 * @param {shapy.browser.Asset.Type} type Type of asset for which we return name
 */
shapy.browser.Service.prototype.defaultName = function(type) {
  switch (type) {
    case shapy.browser.Asset.Type.DIRECTORY: return 'Untitled Folder';
    case shapy.browser.Asset.Type.SCENE:     return 'Untitled Scene';
    case shapy.browser.Asset.Type.TEXTURE:   return 'Untitled Texture';
    default:                                 return 'Untitled';
  }
};

/**
 * Creates a new asset.
 *
 * @private
 *
 * @param {string}   url  URL of the resource.
 * @param {!Object<string, Object>} cache Cache for the resource.
 * @param {Function} cons Asset constructor.
 *
 * @return {!angular.$q} Promise to return a new asset.
 */
shapy.browser.Service.prototype.create_ = function(url, cache, cons) {
  var parent = this.current;
  // Request new asset from server.
  return this.http_.post(url, { parent: parent.id })
      .then(goog.bind(function(response) {
        // Create asset representation, cache it, update parent/child refs.
        var asset = new cons(this, response.data.id, response.data);
        cache[asset.id] = asset;
        parent.children.push(asset);
        asset.parent = parent;
      }, this));
};

/**
 * Creates new dir.
 *
 * @return {!angular.$q} Promise to return a new dir.
 */
shapy.browser.Service.prototype.createDir = function() {
  return this.create_('/api/assets/dir', this.dirs_, shapy.browser.Asset.Dir);
};

/**
 * Creates a new scene.
 *
 * @return {!angular.$q} Promise to return a new scene.
 */
shapy.browser.Service.prototype.createScene = function() {
  return this.create_(
      '/api/assets/scene',
      this.scenes_,
      shapy.browser.Asset.Scene);
};

/**
 * Creates a new texture.
 *
 * @return {!angular.$q} Promise to return a new texture.
 */
shapy.browser.Service.prototype.createTexture = function() {
  return this.create_(
      '/api/assets/texture',
      this.textures_,
      shapy.browser.Asset.Texture);
};

/**
 * Fetches asset.
 *
 * @private
 *
 * @param {string}                  url   URL of the resource.
 * @param {!Object<string, Object>} cache Cache for the resource.
 * @param {Function}                cons  Asset constructor.
 * @param {string}                  id    ID of the resource.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.get_ = function(url, cache, cons, id) {
  if (!goog.isDef(id)) {
    return this.q_.reject({ error: 'Invalid ID.' });
  }

  var asset;
  // Check cache.
  if (goog.object.containsKey(cache, id)) {
    asset = cache[id];
    // Return if asset loaded.
    if (asset.ready) {
      return asset.ready.promise;
    }
  } else {
    // Construct (unloaded) asset.
    asset = new cons(this, id);
    cache[id] = asset;
  }

  // Request asset data from server.
  asset.ready = this.q_.defer();
  this.http_.get(url, {params: { id: id }})
    .then(goog.bind(function(response) {
      asset.load(response.data);
      asset.ready.resolve(asset);
    }, this), function() {
      asset.ready.reject();
    });

  return asset.ready.promise;
};

/**
 * Fetches a dir from the server or from local storage.
 *
 * @param {number} dirID ID of the directory.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.getDir = function(dirID) {
  return this.get_(
      '/api/assets/dir',
      this.dirs_,
      shapy.browser.Asset.Dir,
      dirID
  );
};

/**
 * Fetches a scene from the server or from local storage.
 *
 * @param {number} sceneID ID of the scene.
 *
 * @return {!angular.$q} Promise to return the scene.
 */
shapy.browser.Service.prototype.getScene = function(sceneID) {
  return this.get_(
      '/api/assets/scene',
      this.scenes_,
      shapy.browser.Asset.Scene,
      sceneID
  );
};

/**
 * Fetches a texture from the server or from local storage.
 *
 * @param {number} textureID ID of the scene.
 *
 * @return {!angular.$q} Promise to return the scene.
 */
shapy.browser.Service.prototype.getTexture = function(textureID) {
  return this.get_(
      '/api/assets/textures',
      this.textures_,
      shapy.browser.Asset.Texture,
      textureID
  );
};

/**
 * Fetches public space from the server or from local storage.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.getPublic = function() {
  return this.get_(
      '/api/assets/public',
      this.dirs_,
      shapy.browser.Asset.Dir,
      shapy.browser.Asset.Space.PUBLIC
  );
};

/**
 * Fetches shared space from the server or from local storage.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.getShared = function() {
  return this.get_(
      '/api/assets/shared',
      this.dirs_,
      shapy.browser.Asset.Dir,
      shapy.browser.Asset.Space.SHARED
  );
};

/**
 * Fetches filtered space from the server or from local storage.
 *
 * @return {!angular.$q}
 */
shapy.browser.Service.prototype.getFiltered = function(id) {
  return this.get_(
      '/api/assets/filtered',
      this.dirs_,
      shapy.browser.Asset.Dir,
      id
  );
};

/**
 * Renames asset.
 *
 * @param {string}               url   URL of the resource.
 * @param {!shapy.browser.Asset} asset Asset to rename.
 * @param {string}               name  New name.
 */
shapy.browser.Service.prototype.rename_ = function(url, asset, name) {
  this.http_.put(url, {
    id: asset.id,
    name: name
  }).then(function() {
      asset.name = name;
  });
};

/**
 * Renames dir.
 *
 * @param {!shapy.browser.Asset.Dir} dir  Dir to rename.
 * @param {string}                   name New name.
 */
shapy.browser.Service.prototype.renameDir = function(dir, name) {
  this.rename_('/api/assets/dir', dir, name);
};

/**
 * Renames scene.
 *
 * @param {!shapy.browser.Asset.Scene} scene Scene to rename.
 * @param {string}                     name  New name.
 */
shapy.browser.Service.prototype.renameScene = function(scene, name) {
  this.rename_('/api/assets/scene', scene, name);
};

/**
 * Deletes asset.
 *
 * @param {string}                  url   URL of the resource.
 * @param {!shapy.browser.Asset}    asset Asset to rename.
 * @param {!Object<string, Object>} cache Cache for the resource.
 */
shapy.browser.Service.prototype.delete_ = function(url, asset, cache) {
  // Early return for faulty deletes
  if (asset.id <= 0)
    return;

  // Delete on server
  this.http_.delete(url, {params: {id: asset.id}}).then(goog.bind(function() {
    // Update parent
    this.current.children = this.current.children.filter(function(child) {
      return asset.id !== child.id;
    });
    // Clean cache
    this.removefromCache(asset);
  }, this));
};

/**
 * Recursively removes assets from caches
 *
 * @param {!shapy.browser.Asset}    asset Asset which (with children) we remove.
 */
shapy.browser.Service.prototype.removefromCache = function(asset) {
  switch (asset.type) {
    case shapy.browser.Asset.Type.DIRECTORY:
      goog.object.remove(asset.shBrowser_.dirs_, asset.id);
      break;
    case shapy.browser.Asset.Type.SCENE:
      goog.object.remove(asset.shBrowser_.scenes_, asset.id);
      break;
    case shapy.browser.Asset.Type.TEXTURE:
      goog.object.remove(asset.shBrowser_.textures_, asset.id);
      break;
  }
  if (asset.children) {
    goog.array.forEach(asset.children, asset.shBrowser_.removefromCache);
  }
};

/**
 * Deletes dir.
 *
 * @param {!shapy.browser.Asset.Dir} dir  Dir to delete.
 */
shapy.browser.Service.prototype.deleteDir = function(dir) {
  this.delete_('/api/assets/dir', dir, this.dirs_);
};

/**
 * Deletes scene.
 *
 * @param {!shapy.browser.Asset.Scene} scene Scene to delete.
 */
shapy.browser.Service.prototype.deleteScene = function(scene) {
  this.delete_('/api/assets/scene', scene, this.scenes_);
};

<|endoftext|>