<commit_before>import Ember from 'ember';
import DS    from 'ember-data';

// Give it an array and an action function that returns
// a promise. It will call the action function for each item
// in the provided array and will wait for the returned
// promise to be resolved before going to the next item
// in the array. The function itself returns a promise
// so that you know when the whole array has been processed.
export function forEachWait(array, actionFunction, index) {
  var rsvpName = '';
  if (array.get === undefined){array = Ember.A(array);}// convert to ember array if not one already
  if (array.length && array.length > 0){
    rsvpName = 'forEachPromise ' + array.get('firstObject').constructor + ' size: '  + array.length;
  }else{
    rsvpName = 'forEachPromise Zero length: ' + array;
  }

  return new Ember.RSVP.Promise(function(arrayResolve){
    // Set up the array index for recursion counting
    if (index === undefined){
      index = 0;
    }else{
      index += 1;
    }

    // If each item has been processed or the array was empty, resolve
    if (array.length === 0 || array.length === index){
      arrayResolve();
      return;
    }

    var item = array.objectAt(index);
    if(item === undefined){
      arrayResolve();
      return;// Catch the edge case where item is undefined.
    }

    // Call action function and then..
    actionFunction(item).then(function () {
      // A little recursion here...
      // Process the next item in the array.
      forEachWait(array, actionFunction, index).then(function () {
        // Let the caller know the array is completed.
        arrayResolve();
      });
    });// end action function then
  }, rsvpName);// end rsvp

}

export function deepSaveArray(items) {
  var finalSaveRsvp = new Ember.RSVP.Promise(function(finalSaveResolve) {
    var saveRsvps = [];
    var item;
    for(var i = 0; item = items[i]; i++){
      saveRsvps.push( item.deepSave() );
    }

    Ember.RSVP.all(saveRsvps).then(function () {
      finalSaveResolve(items);
    });
  });// end allSaveRsvp

  return finalSaveRsvp;
}

export var AssociationDescriptor = Ember.Object.extend({
  attrName:      null,// attr name on parent
  object:        null,// the child
  parent:        null,// the parent
  collection:    null,// collection child is from
  level:         0,

  // Get title from the config where the relationship was defined.
  title: Ember.computed('parent', 'attrName', function () {
    var attrName = this.get('attrName');
    var parent = this.get('parent');
    if (parent === null){return '';}
    return parent.get(attrName + 'Title');
  }),

  // Get display groups from the config where the relationship was defined.
  displayGroups: Ember.computed('parent', 'attrName', function () {
    var attrName = this.get('attrName');
    var parent = this.get('parent');
    if (parent === null){return [];}
    return parent.get(attrName + 'DisplayGroups');
  }),

  // This adds support for `inDefaultDisplayGroup` and `inFooDisplayGroup`
  unknownProperty: function (key) {
    var matches = key.match(/in([a-zA-Z0-9]+)DisplayGroup/);
    if (matches){
      var displayGroup = matches[1].substr(0, 1).toLowerCase() + matches[1].substr(1);
      return this.inDisplayGroup(displayGroup);
    }else {
      return this._super(arguments);
    }
  },

  // Always report being in a display group for level zero
  // since there is no way for a display group to be defined
  // for a level zero association.
  inDisplayGroup: function(displayGroup){
    var level = this.get('level');
    if(level === 0 || this.get('displayGroups').indexOf(displayGroup) > -1){
      return true;
    }
    return false;
  },

});

// These are helpers for looping over child objects
export default function IstModelChildrenHelpers(modelConfig) {
  var newModel = {};

  newModel.helpers = {
    forEachWait: forEachWait,

    processChildNames: function (model, callback, childNames, level) {
      var helpers = this;// this referes to 'helpers'

      return new Ember.RSVP.Promise(function(processChildNamesResolve) {
        var loopRsvp = forEachWait(childNames, function (childName) {
          return new Ember.RSVP.Promise(function(innerChildNameResolve) {

            var childRsvp = model.get(childName);
            childRsvp.then(function (foundChildren) {
              var childMeta = AssociationDescriptor.create({
                attrName:      childName,
                object:        null,// Will set later
                collection:    null,// Will set later if comes from a collection
                level:         level,
                parent:        model,
              });

              helpers.processFoundChildren(foundChildren, callback, childMeta).then(function () {
                innerChildNameResolve();
              });
            });

          }, 'processChildNames inner' + childName);
        });

        loopRsvp.then(function () {
          processChildNamesResolve();
        });

      }, 'processChildNames ' + model + ' ' + childNames);// end processChildNames RSVP

    },// end processChildNames

    processFoundChildren: function (foundChildren, callback, childMeta) {
      var self = this;
      var rsvpName = '';

      if (foundChildren && foundChildren.length && foundChildren.length > 0){
        rsvpName = 'processFoundChildren ' + foundChildren.get('firstObject').constructor + ' size: '  + foundChildren.length;
      }else if(foundChildren === null){
        rsvpName = 'processFoundChildren NULL';
      }else{
        rsvpName = 'processFoundChildren ' + foundChildren.constructor;
      }

      return new Ember.RSVP.Promise(function(processFoundChildrenResolve) {
        if (foundChildren === null){
          // I guess the child has no value.
          processFoundChildrenResolve();
          return;
        }else if(Ember.isBlank(foundChildren.get('length')) ){
          // Child was a hasOne.
          var child = foundChildren;
          childMeta.set('object', child);
          self.processChild(child, callback, childMeta).then(function () {
            processFoundChildrenResolve();
          });
        } else {
          // Child is a hasMany
          var loopRsvp = forEachWait(foundChildren, function (child) {

            // Creat a new object for this child in the has many.
            var thisChildMeta = AssociationDescriptor.create({
              object:        child,
              attrName:      childMeta.get('attrName'),
              collection:    foundChildren,
              level:         childMeta.get('level'),
              parent:        childMeta.get('parent'),
            });
            // return that promise so the loop can wait for it to be done.
            return self.processChild(child, callback, thisChildMeta);
          });

          loopRsvp.then(function () {
            processFoundChildrenResolve();
          });
        }
      }, 'processFoundChildren ' + foundChildren);// end processFoundChildren.rsvp

    },//end processFoundChildren

    processChild: function (child, callback, childMeta) {
      return new Ember.RSVP.Promise(function(processChildResolve) {
        if(child.everyChildAssociation === undefined){
          processChildResolve();// not a model builder object. Can't go any deeper
          return;
        }

        // If it's a proxy to, wait for proxy to be loaded as well.
        if(child.get('proxyId') !== undefined && child.get('proxyId') !== null && child.get('content').then){
          child.get('content').then(function(){
            child.everyChildAssociation(callback, childMeta).then(function () {
              processChildResolve();
              return;
            });// end everyChildAssociation.then

          });
        }else {

          child.everyChildAssociation(callback, childMeta).then(function () {
            processChildResolve();
            return;
          });// end everyChildAssociation.then

        }

      }, 'processChild '+ child.constructor + ' ' + child );// processChild rsvp
    }// end processChild()

  };// end helpers hash

  // Returns an array of all deeply nested children for you to loop over.
  // Each item has a few properties like the title from the parent's hasMany definition
  // and the .level into the stack. To get the action child use .object
  // There is also a inDisplayGroup() function so you can see if that association
  // is in a particular display group.
  // Note, the first item this returns is the model itself
  newModel.childAssociations = Ember.computed('childAssociationDidChange', function(){
    var self = this;
    var out = Ember.A([]);
    var finalResolve;

    var promise = new Ember.RSVP.Promise(function(resolve) {
      finalResolve = resolve;// pass this to outer scope.
    });

    // make an array to return.
    var promiseArray =  DS.PromiseArray.create({
      promise: promise,
      content: out
    });

    // Push associations on the the promise array's content.
    self.everyChildAssociation(function (assoc) {
      promiseArray.content.pushObject(assoc);
    }).then(function(){
      // report back that we are all done.
      finalResolve(promiseArray.content);
    });

    return promiseArray;
  });

  // Same as .childAssociations() but does a callback whenver
  // a child has finished loading.
  newModel.everyChildAssociation = function(callback, childMeta){
    var self = this;

    if (childMeta === undefined){
      childMeta = AssociationDescriptor.extend({
        title:        Ember.computed.alias('object.displayTitle'),
        diplayGroups: [],
      }).create({
        //attrName:      'self',
        object:        self,
        collection:    [],
        level:         0,
      });
    } else {

    }

    callback(childMeta);
    var nextLevel = childMeta.level + 1;

    var rsvp = new Ember.RSVP.Promise(function(everyChildAssociationResolve) {

      var childNames = self.definedChildAssociations.slice(0);// clone array
      if (childNames.length === 0 ) {
        everyChildAssociationResolve();// no children to process. Just resolve.
        return;
      }

      var eachChildRsvp = new Ember.RSVP.Promise(function(eachChildResolve) {
        // Process each child, then we're notify that we're done.
        self.helpers.processChildNames(self, callback, childNames, nextLevel).then(function () {
          eachChildResolve();
          return;
        }, 'everyChildAssociation -> eachChildRsvp ' + self);

      }, 'eachChildRsvp ' + childNames + ' ' + self);

      // All children are done. Pass the word on.
      eachChildRsvp.then(function () {
        everyChildAssociationResolve();
        return;
      });

    }, 'everyChildAssociation ' + self); // end rsvp
    return rsvp;
  };//end everyChildAssociation

  // When saving, you need to save belongsTo first so that
  // they have an ID to store. After they have all been save,
  // then you can save your self and the hasManys.
  newModel.deepSave = function () {
    var self = this;
    return new Ember.RSVP.Promise(function(finalSaveResolve) {
      self.deepSaveBelongsTo().then(function (saved) {
        saved.deepSaveHasMany().then(function (savedAgain) {
          finalSaveResolve(savedAgain);
        });
      });
    });
  };

  // In order to save a model all the belongTo relationships need
  // to have an ID set. This function saves all the belongsTo
  // relationships then saves itself.
  newModel.deepSaveBelongsTo = function () {
    var self = this;
    var hasManys = this.get('definedHasManyAssociations');
    var childSaveRsvps = Ember.A();// Keep a list of children we are saving.

    var finalSaveRsvp = new Ember.RSVP.Promise(function(finalSaveResolve) {
      // Get all children
      self.get('childAssociations').then(function (childAssociations) {
        childAssociations.forEach(function (childAssoc) {
          if (childAssoc.level === 0){return;}// skip self. Will save last.
          if (hasManys.indexOf(childAssoc.attrName) > -1) {return;}// don't save any hasManys

          // Only go one level deep - let the child save it's own children.
          if (childAssoc.level === 1){
            if (childAssoc.object.deepSaveBelongsTo){
              childSaveRsvps.pushObject( childAssoc.object.deepSaveBelongsTo() );
            }else{
              // support non model builder models
              childSaveRsvps.pushObject( childAssoc.object.save() );
            }
          }
        });

        // Wait for all children to be saved, then do final resolve
        Ember.RSVP.all(childSaveRsvps, 'deepSaveBelongsTo').then(function () {
          self.save().then(function(savedSelf){
            finalSaveResolve(savedSelf);
          });
        });

      });// end get childAssociations

    });
    return finalSaveRsvp;
  };

  //  TODO: Can't do hasOne because it's belongs to and will need to save
  //        the child first.
  // Save yourself, then save all of your hasMany or hasOne relationships
  // TODO: I think there might be a bug where this only works one level deep.
  // It wont wait for a the child's child's parent to be saved.
  newModel.deepSaveHasMany = function () {
    var self           = this;
    var hasManys       = this.get('definedHasManyAssociations');
    var childSaveRsvps = Ember.A();// Keep a list of children we are saving.

    var finalSaveRsvp = new Ember.RSVP.Promise(function(finalSaveResolve) {
      // Save your self
      self.save().then(function (savedSelf) {
        // Then get children
        savedSelf.get('childAssociations').then(function (childAssociations) {

          childAssociations.forEach(function (childAssoc) {
            if (childAssoc.level === 0){return;}// skip self. already saved
            if (hasManys.indexOf(childAssoc.attrName) === -1) {return;}// don't save belongs to.

            if (childAssoc.level === 1){
              if (childAssoc.object.deepSaveHasMany){
                // create recursion where it will save itself, wait, then save level 1 children.
                childSaveRsvps.pushObject( childAssoc.object.deepSaveHasMany() );
              }else{
                // support non model builder models
                childSaveRsvps.pushObject( childAssoc.object.save() );
              }
            }
          });

          // Wait for all children to be saved, then do final resolve
          Ember.RSVP.all(childSaveRsvps, 'deepSaveHasMany').then(function () {
            finalSaveResolve(self);
          });

        });// end get childAssociations
      });// end self seave

    });
    return finalSaveRsvp;
  };

  // Save yourself, then save all of your hasMany or hasOne relationships
  newModel.deepDestroy = function () {
    var self       = this;
    var childRsvps = Ember.A();

    var finalRsvp = new Ember.RSVP.Promise(function(finalResolve) {
      self.get('childAssociations').then(function (childAssociations) {
        // Delete the children first.
        var reverseChildAssociations = childAssociations.reverse();
        reverseChildAssociations.forEach(function (childAssoc) {
          childRsvps.pushObject( childAssoc.object.destroyRecord() );
        });

        // Wait for all children to be saved, then do final resolve
        Ember.RSVP.all(childRsvps, 'deepDestroy').then(function () {
          finalResolve();
        });

      });// end get childAssociations
    });
    return finalRsvp;
  };

  // Figure out which children this model has
  var childPropsToWatch = [];
  for(var attrName in (modelConfig.attributes || {}) ){
    var attrConfig = modelConfig.attributes[attrName];

    if (attrConfig.hasOne !== undefined){
      // Watch for when the child is added/removed
      childPropsToWatch.push(attrName + '.id');
      childPropsToWatch.push(attrName + '.childAssociationDidChange');
    }

    if (attrConfig.hasMany !== undefined) {
      childPropsToWatch.push(attrName + '.@each.childAssociationDidChange');
    }
  }//end foreach

  childPropsToWatch.push(function(){
    this.incrementProperty('childAssociationDidChange');
  });

  // Wrap childPropsToWatch in quotes and join together
  // to make somethign we can pass to .property() in an eval.
  // Returns: " 'fooId', 'bar.@each.childAssociationDidChange' "
  // var propsToObserve = childPropsToWatch.map(function(c){
  //   return "'" + c + "'";
  // }).join(',');

  // The childAssociationDidChange is a property that will
  // change whenever a child has been added / removed.
  // It wont tell us if any attributes way down in the
  // nest have been changed.
  newModel.childAssociationDidChange = 0;
  newModel.childAssocationChangeObserver = Ember.observer.apply(newModel, childPropsToWatch);

  //propsToObserve = "newModel.childAssocationChangeObserver.observes("+propsToObserve+")";
  //newModel.childAssocationChangeObserver = eval(propsToObserve);

  return newModel;
}// end function

<commit_msg>fix for ember 2.2.0<commit_after>import Ember from 'ember';
import DS    from 'ember-data';

// Give it an array and an action function that returns
// a promise. It will call the action function for each item
// in the provided array and will wait for the returned
// promise to be resolved before going to the next item
// in the array. The function itself returns a promise
// so that you know when the whole array has been processed.
export function forEachWait(array, actionFunction, index) {
  var rsvpName = '';
  if (array.get === undefined){array = Ember.A(array);}// convert to ember array if not one already
  if (array.length && array.length > 0){
    rsvpName = 'forEachPromise ' + array.get('firstObject').constructor + ' size: '  + array.length;
  }else{
    rsvpName = 'forEachPromise Zero length: ' + array;
  }

  return new Ember.RSVP.Promise(function(arrayResolve){
    // Set up the array index for recursion counting
    if (index === undefined){
      index = 0;
    }else{
      index += 1;
    }

    // If each item has been processed or the array was empty, resolve
    if (array.length === 0 || array.length === index){
      arrayResolve();
      return;
    }

    var item = array.objectAt(index);
    if(item === undefined){
      arrayResolve();
      return;// Catch the edge case where item is undefined.
    }

    // Call action function and then..
    actionFunction(item).then(function () {
      // A little recursion here...
      // Process the next item in the array.
      forEachWait(array, actionFunction, index).then(function () {
        // Let the caller know the array is completed.
        arrayResolve();
      });
    });// end action function then
  }, rsvpName);// end rsvp

}

export function deepSaveArray(items) {
  var finalSaveRsvp = new Ember.RSVP.Promise(function(finalSaveResolve) {
    var saveRsvps = [];
    var item;
    for(var i = 0; item = items[i]; i++){
      saveRsvps.push( item.deepSave() );
    }

    Ember.RSVP.all(saveRsvps).then(function () {
      finalSaveResolve(items);
    });
  });// end allSaveRsvp

  return finalSaveRsvp;
}

export var AssociationDescriptor = Ember.Object.extend({
  attrName:      null,// attr name on parent
  object:        null,// the child
  parent:        null,// the parent
  collection:    null,// collection child is from
  level:         0,

  // Get title from the config where the relationship was defined.
  title: Ember.computed('parent', 'attrName', function () {
    var attrName = this.get('attrName');
    var parent = this.get('parent');
    if (parent === null){return '';}
    return parent.get(attrName + 'Title');
  }),

  // Get display groups from the config where the relationship was defined.
  displayGroups: Ember.computed('parent', 'attrName', function () {
    var attrName = this.get('attrName');
    var parent = this.get('parent');
    if (parent === null){return [];}
    return parent.get(attrName + 'DisplayGroups');
  }),

  // This adds support for `inDefaultDisplayGroup` and `inFooDisplayGroup`
  unknownProperty: function (key) {
    var matches = key.match(/in([a-zA-Z0-9]+)DisplayGroup/);
    if (matches){
      var displayGroup = matches[1].substr(0, 1).toLowerCase() + matches[1].substr(1);
      return this.inDisplayGroup(displayGroup);
    }else {
      return this._super(arguments);
    }
  },

  // Always report being in a display group for level zero
  // since there is no way for a display group to be defined
  // for a level zero association.
  inDisplayGroup: function(displayGroup){
    var level = this.get('level');
    if(level === 0 || this.get('displayGroups').indexOf(displayGroup) > -1){
      return true;
    }
    return false;
  },

});

// These are helpers for looping over child objects
export default function IstModelChildrenHelpers(modelConfig) {
  var newModel = {};

  newModel.helpers = {
    forEachWait: forEachWait,

    processChildNames: function (model, callback, childNames, level) {
      var helpers = this;// this referes to 'helpers'

      return new Ember.RSVP.Promise(function(processChildNamesResolve) {
        var loopRsvp = forEachWait(childNames, function (childName) {
          return new Ember.RSVP.Promise(function(innerChildNameResolve) {

            var childRsvp = model.get(childName);
            childRsvp.then(function (foundChildren) {
              var childMeta = AssociationDescriptor.create({
                attrName:      childName,
                object:        null,// Will set later
                collection:    null,// Will set later if comes from a collection
                level:         level,
                parent:        model,
              });

              helpers.processFoundChildren(foundChildren, callback, childMeta).then(function () {
                innerChildNameResolve();
              });
            });

          }, 'processChildNames inner' + childName);
        });

        loopRsvp.then(function () {
          processChildNamesResolve();
        });

      }, 'processChildNames ' + model + ' ' + childNames);// end processChildNames RSVP

    },// end processChildNames

    processFoundChildren: function (foundChildren, callback, childMeta) {
      var self = this;
      var rsvpName = '';

      if (foundChildren && foundChildren.length && foundChildren.length > 0){
        rsvpName = 'processFoundChildren ' + foundChildren.get('firstObject').constructor + ' size: '  + foundChildren.length;
      }else if(foundChildren === null){
        rsvpName = 'processFoundChildren NULL';
      }else{
        rsvpName = 'processFoundChildren ' + foundChildren.constructor;
      }

      return new Ember.RSVP.Promise(function(processFoundChildrenResolve) {
        if (foundChildren === null){
          // I guess the child has no value.
          processFoundChildrenResolve();
          return;
        }else if(Ember.isBlank(foundChildren.get('length')) ){
          // Child was a hasOne.
          var child = foundChildren;
          childMeta.set('object', child);
          self.processChild(child, callback, childMeta).then(function () {
            processFoundChildrenResolve();
          });
        } else {
          // Child is a hasMany
          var loopRsvp = forEachWait(foundChildren, function (child) {

            // Creat a new object for this child in the has many.
            var thisChildMeta = AssociationDescriptor.create({
              object:        child,
              attrName:      childMeta.get('attrName'),
              collection:    foundChildren,
              level:         childMeta.get('level'),
              parent:        childMeta.get('parent'),
            });
            // return that promise so the loop can wait for it to be done.
            return self.processChild(child, callback, thisChildMeta);
          });

          loopRsvp.then(function () {
            processFoundChildrenResolve();
          });
        }
      }, 'processFoundChildren ' + foundChildren);// end processFoundChildren.rsvp

    },//end processFoundChildren

    processChild: function (child, callback, childMeta) {
      return new Ember.RSVP.Promise(function(processChildResolve) {
        if(child.everyChildAssociation === undefined){
          processChildResolve();// not a model builder object. Can't go any deeper
          return;
        }

        // If it's a proxy to, wait for proxy to be loaded as well.
        if(child.get('proxyId') !== undefined && child.get('proxyId') !== null && child.get('content').then){
          child.get('content').then(function(){
            child.everyChildAssociation(callback, childMeta).then(function () {
              processChildResolve();
              return;
            });// end everyChildAssociation.then

          });
        }else {

          child.everyChildAssociation(callback, childMeta).then(function () {
            processChildResolve();
            return;
          });// end everyChildAssociation.then

        }

      }, 'processChild '+ child.constructor + ' ' + child );// processChild rsvp
    }// end processChild()

  };// end helpers hash

  // Returns an array of all deeply nested children for you to loop over.
  // Each item has a few properties like the title from the parent's hasMany definition
  // and the .level into the stack. To get the action child use .object
  // There is also a inDisplayGroup() function so you can see if that association
  // is in a particular display group.
  // Note, the first item this returns is the model itself
  newModel.childAssociations = Ember.computed('childAssociationDidChange', function(){
    var self = this;
    var out = Ember.A([]);
    var finalResolve;

    var promise = new Ember.RSVP.Promise(function(resolve) {
      finalResolve = resolve;// pass this to outer scope.
    });

    // make an array to return.
    var promiseArray =  DS.PromiseArray.create({
      promise: promise,
      content: out
    });

    // Push associations on the the promise array's content.
    self.everyChildAssociation(function (assoc) {
      out.push(assoc);
    }).then(function(){
      // report back that we are all done.
      finalResolve(out);
    });

    return promiseArray;
  });

  // Same as .childAssociations() but does a callback whenver
  // a child has finished loading.
  newModel.everyChildAssociation = function(callback, childMeta){
    var self = this;

    if (childMeta === undefined){
      childMeta = AssociationDescriptor.extend({
        title:        Ember.computed.alias('object.displayTitle'),
        diplayGroups: [],
      }).create({
        //attrName:      'self',
        object:        self,
        collection:    [],
        level:         0,
      });
    } else {

    }

    callback(childMeta);
    var nextLevel = childMeta.level + 1;

    var rsvp = new Ember.RSVP.Promise(function(everyChildAssociationResolve) {

      var childNames = self.definedChildAssociations.slice(0);// clone array
      if (childNames.length === 0 ) {
        everyChildAssociationResolve();// no children to process. Just resolve.
        return;
      }

      var eachChildRsvp = new Ember.RSVP.Promise(function(eachChildResolve) {
        // Process each child, then we're notify that we're done.
        self.helpers.processChildNames(self, callback, childNames, nextLevel).then(function () {
          eachChildResolve();
          return;
        }, 'everyChildAssociation -> eachChildRsvp ' + self);

      }, 'eachChildRsvp ' + childNames + ' ' + self);

      // All children are done. Pass the word on.
      eachChildRsvp.then(function () {
        everyChildAssociationResolve();
        return;
      });

    }, 'everyChildAssociation ' + self); // end rsvp
    return rsvp;
  };//end everyChildAssociation

  // When saving, you need to save belongsTo first so that
  // they have an ID to store. After they have all been save,
  // then you can save your self and the hasManys.
  newModel.deepSave = function () {
    var self = this;
    return new Ember.RSVP.Promise(function(finalSaveResolve) {
      self.deepSaveBelongsTo().then(function (saved) {
        saved.deepSaveHasMany().then(function (savedAgain) {
          finalSaveResolve(savedAgain);
        });
      });
    });
  };

  // In order to save a model all the belongTo relationships need
  // to have an ID set. This function saves all the belongsTo
  // relationships then saves itself.
  newModel.deepSaveBelongsTo = function () {
    var self = this;
    var hasManys = this.get('definedHasManyAssociations');
    var childSaveRsvps = Ember.A();// Keep a list of children we are saving.

    var finalSaveRsvp = new Ember.RSVP.Promise(function(finalSaveResolve) {
      // Get all children
      self.get('childAssociations').then(function (childAssociations) {
        childAssociations.forEach(function (childAssoc) {
          if (childAssoc.level === 0){return;}// skip self. Will save last.
          if (hasManys.indexOf(childAssoc.attrName) > -1) {return;}// don't save any hasManys

          // Only go one level deep - let the child save it's own children.
          if (childAssoc.level === 1){
            if (childAssoc.object.deepSaveBelongsTo){
              childSaveRsvps.pushObject( childAssoc.object.deepSaveBelongsTo() );
            }else{
              // support non model builder models
              childSaveRsvps.pushObject( childAssoc.object.save() );
            }
          }
        });

        // Wait for all children to be saved, then do final resolve
        Ember.RSVP.all(childSaveRsvps, 'deepSaveBelongsTo').then(function () {
          self.save().then(function(savedSelf){
            finalSaveResolve(savedSelf);
          });
        });

      });// end get childAssociations

    });
    return finalSaveRsvp;
  };

  //  TODO: Can't do hasOne because it's belongs to and will need to save
  //        the child first.
  // Save yourself, then save all of your hasMany or hasOne relationships
  // TODO: I think there might be a bug where this only works one level deep.
  // It wont wait for a the child's child's parent to be saved.
  newModel.deepSaveHasMany = function () {
    var self           = this;
    var hasManys       = this.get('definedHasManyAssociations');
    var childSaveRsvps = Ember.A();// Keep a list of children we are saving.

    var finalSaveRsvp = new Ember.RSVP.Promise(function(finalSaveResolve) {
      // Save your self
      self.save().then(function (savedSelf) {
        // Then get children
        savedSelf.get('childAssociations').then(function (childAssociations) {

          childAssociations.forEach(function (childAssoc) {
            if (childAssoc.level === 0){return;}// skip self. already saved
            if (hasManys.indexOf(childAssoc.attrName) === -1) {return;}// don't save belongs to.

            if (childAssoc.level === 1){
              if (childAssoc.object.deepSaveHasMany){
                // create recursion where it will save itself, wait, then save level 1 children.
                childSaveRsvps.pushObject( childAssoc.object.deepSaveHasMany() );
              }else{
                // support non model builder models
                childSaveRsvps.pushObject( childAssoc.object.save() );
              }
            }
          });

          // Wait for all children to be saved, then do final resolve
          Ember.RSVP.all(childSaveRsvps, 'deepSaveHasMany').then(function () {
            finalSaveResolve(self);
          });

        });// end get childAssociations
      });// end self seave

    });
    return finalSaveRsvp;
  };

  // Save yourself, then save all of your hasMany or hasOne relationships
  newModel.deepDestroy = function () {
    var self       = this;
    var childRsvps = Ember.A();

    var finalRsvp = new Ember.RSVP.Promise(function(finalResolve) {
      self.get('childAssociations').then(function (childAssociations) {
        // Delete the children first.
        var reverseChildAssociations = childAssociations.reverse();
        reverseChildAssociations.forEach(function (childAssoc) {
          childRsvps.pushObject( childAssoc.object.destroyRecord() );
        });

        // Wait for all children to be saved, then do final resolve
        Ember.RSVP.all(childRsvps, 'deepDestroy').then(function () {
          finalResolve();
        });

      });// end get childAssociations
    });
    return finalRsvp;
  };

  // Figure out which children this model has
  var childPropsToWatch = [];
  for(var attrName in (modelConfig.attributes || {}) ){
    var attrConfig = modelConfig.attributes[attrName];

    if (attrConfig.hasOne !== undefined){
      // Watch for when the child is added/removed
      childPropsToWatch.push(attrName + '.id');
      childPropsToWatch.push(attrName + '.childAssociationDidChange');
    }

    if (attrConfig.hasMany !== undefined) {
      childPropsToWatch.push(attrName + '.@each.childAssociationDidChange');
    }
  }//end foreach

  childPropsToWatch.push(function(){
    this.incrementProperty('childAssociationDidChange');
  });

  // Wrap childPropsToWatch in quotes and join together
  // to make somethign we can pass to .property() in an eval.
  // Returns: " 'fooId', 'bar.@each.childAssociationDidChange' "
  // var propsToObserve = childPropsToWatch.map(function(c){
  //   return "'" + c + "'";
  // }).join(',');

  // The childAssociationDidChange is a property that will
  // change whenever a child has been added / removed.
  // It wont tell us if any attributes way down in the
  // nest have been changed.
  newModel.childAssociationDidChange = 0;
  newModel.childAssocationChangeObserver = Ember.observer.apply(newModel, childPropsToWatch);

  //propsToObserve = "newModel.childAssocationChangeObserver.observes("+propsToObserve+")";
  //newModel.childAssocationChangeObserver = eval(propsToObserve);

  return newModel;
}// end function
<|endoftext|>


<commit_before>import "../polyfill/dom";
import {isElement} from "./utils";

/**
 * Returns whether the given element is - in fact - an HTMLElement and
 * it matches the optional selector
 *
 * @param {Node|HTMLElement} element
 * @param {?string} selector
 * @return {boolean}
 */
function elementMatches (element, selector)
{
    return isElement(element) && (null === selector || element.matches(selector));
}

/**
 * Finds all DOM elements matching the selector
 *
 * @param {string} selector
 * @param {Document|HTMLElement} context
 * @return {HTMLElement[]}
 */
export function find (selector, context = document)
{
    return Array.prototype.slice.call(context.querySelectorAll(selector));
}

/**
 * Finds a single DOM node matching the selector
 *
 * @param {string} selector
 * @param {Document|HTMLElement} context
 * @return {?HTMLElement}
 */
export function findOne (selector, context = document)
{
    return context.querySelector(selector);
}

/**
 * Filters a list of DOM elements that match the given selector
 *
 * @param {HTMLElement[]} list
 * @param {string} selector
 * @return {HTMLElement[]}
 */
export function filter (list, selector)
{
    return list.filter(
        (e) => e.matches(selector)
    );
}

/**
 * Filters a list of DOM elements that DO NOT match the given selector
 *
 * @param {HTMLElement[]} list
 * @param {string} selector
 * @return {HTMLElement[]}
 */
export function not (list, selector)
{
    return list.filter(
        (e) => !e.matches(selector)
    );
}

/**
 * Returns all children
 *
 * @param {HTMLElement} parent
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function children (parent, selector = null)
{
    const list = [];
    let child = parent.firstChild;

    while (child)
    {
        if (elementMatches(child, selector))
        {
            list.push(child);
        }

        child = child.nextSibling;
    }

    return list;
}

/**
 * Returns the nearest previous sibling matching
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {?HTMLElement}
 */
export function prev (element, selector = null)
{
    let sibling;

    while (sibling = element.previousSibling)
    {
        if (elementMatches(sibling, selector))
        {
            return sibling;
        }
    }

    return null;
}

/**
 * Returns the nearest next sibling
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {?HTMLElement}
 */
export function next (element, selector = null)
{
    let sibling;

    while (sibling = element.nextSibling)
    {
        if (elementMatches(sibling, selector))
        {
            return sibling;
        }
    }

    return null;
}

/**
 * Returns all siblings
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function siblings (element, selector = null)
{
    let sibling = element.parentNode.firstChild;
    const list = [];

    while (sibling)
    {
        if (sibling !== element && elementMatches(sibling, selector))
        {
            list.push(element);
        }

        sibling = element.nextSibling;
    }

    return list;
}
<commit_msg>Implement `prevAll()` and `nextAll()`<commit_after>import "../polyfill/dom";
import {isElement} from "./utils";

/**
 * Returns whether the given element is - in fact - an HTMLElement and
 * it matches the optional selector
 *
 * @param {Node|HTMLElement} element
 * @param {?string} selector
 * @return {boolean}
 */
function elementMatches (element, selector)
{
    return isElement(element) && (null === selector || element.matches(selector));
}

/**
 * Finds all DOM elements matching the selector
 *
 * @param {string} selector
 * @param {Document|HTMLElement} context
 * @return {HTMLElement[]}
 */
export function find (selector, context = document)
{
    return Array.prototype.slice.call(context.querySelectorAll(selector));
}

/**
 * Finds a single DOM node matching the selector
 *
 * @param {string} selector
 * @param {Document|HTMLElement} context
 * @return {?HTMLElement}
 */
export function findOne (selector, context = document)
{
    return context.querySelector(selector);
}

/**
 * Filters a list of DOM elements that match the given selector
 *
 * @param {HTMLElement[]} list
 * @param {string} selector
 * @return {HTMLElement[]}
 */
export function filter (list, selector)
{
    return list.filter(
        (e) => e.matches(selector)
    );
}

/**
 * Filters a list of DOM elements that DO NOT match the given selector
 *
 * @param {HTMLElement[]} list
 * @param {string} selector
 * @return {HTMLElement[]}
 */
export function not (list, selector)
{
    return list.filter(
        (e) => !e.matches(selector)
    );
}

/**
 * Returns all children
 *
 * @param {HTMLElement} parent
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function children (parent, selector = null)
{
    const list = [];
    let child = parent.firstChild;

    while (child)
    {
        if (elementMatches(child, selector))
        {
            list.push(child);
        }

        child = child.nextSibling;
    }

    return list;
}

/**
 * Returns the nearest previous sibling matching
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {?HTMLElement}
 */
export function prev (element, selector = null)
{
    let sibling;

    while (sibling = element.previousSibling)
    {
        if (elementMatches(sibling, selector))
        {
            return sibling;
        }
    }

    return null;
}

/**
 * Returns the nearest following sibling
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {?HTMLElement}
 */
export function next (element, selector = null)
{
    let sibling;

    while (sibling = element.nextSibling)
    {
        if (elementMatches(sibling, selector))
        {
            return sibling;
        }
    }

    return null;
}

/**
 * Returns all previous siblings
 * (optionally matching the given selector)
 *
 * The nearest sibling is the first element in the list.
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function prevAll (element, selector = null)
{
    let sibling;
    const list = [];

    while (sibling = element.previousSibling)
    {
        if (elementMatches(sibling, selector))
        {
            list.push(sibling);
        }
    }

    return list;
}

/**
 * Returns all following siblings
 * (optionally matching the given selector)
 *
 * The nearest sibling is the first element in the list.
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function nextAll (element, selector = null)
{
    let sibling;
    const list = [];

    while (sibling = element.nextSibling)
    {
        if (elementMatches(sibling, selector))
        {
            list.push(sibling);
        }
    }

    return list;
}

/**
 * Returns all siblings
 * (optionally matching the given selector)
 *
 * @param {HTMLElement} element
 * @param {?string} selector
 * @return {HTMLElement[]}
 */
export function siblings (element, selector = null)
{
    let sibling = element.parentNode.firstChild;
    const list = [];

    while (sibling)
    {
        if (sibling !== element && elementMatches(sibling, selector))
        {
            list.push(element);
        }

        sibling = element.nextSibling;
    }

    return list;
}
<|endoftext|>


<commit_before>import ITool from './itool';
import * as PIXI from 'pixi.js';
import {length, distance} from '../game/helpers';
import Settings from '../game/settings';

export const AnomalyType = [
  { type : 'weightlimit' },
  { type : 'dying' }
];

export default class AnomalyTool extends ITool {
  constructor(stage, level, type) {
    super(stage);

    this.level = level;

    this.settings = new Settings().anomalies;

    // type of the anomaly tool
    this.type = type;

    this.weight_limit_pointer_sprite = null;
    this.dying_road_pointer_sprite = null;

    if(this.type == AnomalyType[0].type) {
      this.current_pointer_sprite = this.getWeightLimitPointerSprite();
    } else if( this.type == AnomalyType[1].type ) {
      this.current_pointer_sprite = this.getDyingRoadPointerSprite();
    }

    this.pointerContainer.addChild(this.current_pointer_sprite);

    this.anomaly_container = new PIXI.Container();

    this.snappedToSegment = null;
    this.snappingDistance = 35;
  }

  getWeightLimitPointerSprite() {
    if(this.weight_limit_pointer_sprite === null) {
      this.weight_limit_pointer_sprite = new PIXI.Text( 0 + 'kg',this.settings.WEIGHT_LIMIT_TEXT_FONT);

      this.weight_limit_pointer_sprite.anchor.set(0.5, 0.5);
      this.weight_limit_pointer_sprite.scale.set(this.settings.WEIGHT_LIMIT_TEXT_SCALE);
    }

    return this.weight_limit_pointer_sprite;
  }

  getDyingRoadPointerSprite() {
    if(this.dying_road_pointer_sprite === null) {
      this.dying_road_pointer_sprite = new PIXI.Text( 0 + 'm',this.settings.DYING_ROAD_TEXT_FONT);

      this.dying_road_pointer_sprite.anchor.set(0.5);
      this.dying_road_pointer_sprite.scale.set(this.settings.DYING_ROAD_TEXT_SCALE);
    }

    return this.dying_road_pointer_sprite;
  }

  mouseMove(mouseInput) {
    var epos = mouseInput.worldPosition;
    this.pointerContainer.position = epos;

    // do a search for nearest node in snapping distance
    this.snappedToSegment = null;
    this.level.refreshRoutes();

    var segments = this.level.getRouteSegments();
    var segment_amount =  segments.length;

    for (var i = 0; i < segment_amount; ++i) {
      var segment = segments[i];
      var segment_pos = {
        x: (segment.endNode.getPos().x + segment.startNode.getPos().x) / 2,
        y: (segment.endNode.getPos().y + segment.startNode.getPos().y) / 2
      };

      if (distance(mouseInput.worldPosition, segment_pos) < this.snappingDistance) {
        this.pointerContainer.position.set(segment_pos.x, segment_pos.y);
        this.snappedToSegment = segment;
        epos = segment_pos;
        break;
      }
    }

    //toggling visibility of pointer if we have snapped and there is anomaly
    if(this.snappedToSegment !== null && this.snappedToSegment.startNode.anomalies.length !== 0) {
      this.snappedToSegment.weight_limit_text.style.fill = 0x00FF00;
      this.snappedToSegment.dying_road_text.style.fill = 0x00FF00;
      this.pointerContainer.visible = false;
    } else {
      this.pointerContainer.visible = true;
    }

  }

  nodeHasAnomalyTo(node,to_node,anomaly_type) {
    var rnode = null;
    var anomaly_index = -1;

    for(var i = 0; i < node.anomalies.length; ++i) {
      if(node.anomalies[i].hasOwnProperty('weight_limit') &&
        node.anomalies[i].to === to_node.getId()) {
        rnode = node;
        anomaly_index = i;
        break;
      }
    }

    return {node:rnode, anomaly_index:anomaly_index};
  }

  mouseUp(mouseInput) {
    // mouse moved aka moved the viewport so don't do the action
    if (length(mouseInput.absDeltaDuringMouseDown) > 20)
      return;

    if(this.snappedToSegment !== null) {
      // weight limit
      if(this.type === AnomalyType[0].type) {
        var node = null;
        var anomaly_index = -1;
        if (this.snappedToSegment.startNode.anomalies.length === 0 && this.snappedToSegment.endNode.anomalies.length === 0) {
          this.snappedToSegment.startNode.anomalies.push({to:this.snappedToSegment.endNode.getId(), weight_limit:1});
          this.snappedToSegment.weight_limit = 1;
          node = this.snappedToSegment.startNode;
          anomaly_index = node.anomalies.length - 1;
        } else {
          // grinding through startnode anomalies
          var node_data = this.nodeHasAnomalyTo(this.snappedToSegment.startNode,this.snappedToSegment.endNode,'weight_limit');

          // if node is not still defined trying endnode
          if(!node_data.node) {
            node_data = this.nodeHasAnomalyTo(this.snappedToSegment.endNode,this.snappedToSegment.startNode,'weight_limit');
          }

          // if we have not found connections we just create it
          if(!node_data.node) {
            this.snappedToSegment.startNode.anomalies.push({to:this.snappedToSegment.endNode.getId(), weight_limit:1});
            this.snappedToSegment.weight_limit = 1;
            node = this.snappedToSegment.startNode;
            anomaly_index = node.anomalies.length - 1;
          } else {
            node = node_data.node;
            anomaly_index = node_data.anomaly_index;
          }
        }

        this.snappedToSegment.weight_limit = node.anomalies[anomaly_index].weight_limit;

      } else if(this.type === AnomalyType[1].type) {
        var node = null;
        var anomaly_index = -1;
        if (this.snappedToSegment.startNode.anomalies.length === 0 && this.snappedToSegment.endNode.anomalies.length === 0) {
          this.snappedToSegment.startNode.anomalies.push({to:this.snappedToSegment.endNode.getId(), dying_road:1});
          this.snappedToSegment.anomalies = this.snappedToSegment.startNode.anomalies;
          node = this.snappedToSegment.startNode;
          anomaly_index = node.anomalies.length - 1;
        } else {
          // grinding through startnode anomalies
          var node_data = this.nodeHasAnomalyTo(this.snappedToSegment.startNode,this.snappedToSegment.endNode,'dying_road');

          // if node is not still defined trying endnode
          if(!node_data.node) {
            node_data = this.nodeHasAnomalyTo(this.snappedToSegment.endNode,this.snappedToSegment.startNode,'dying_road');
          }

          // if we have not found connections we just create it
          if(!node_data.node) {
            this.snappedToSegment.startNode.anomalies.push({to:this.snappedToSegment.endNode.getId(), dying_road:1});
            this.snappedToSegment.anomalies = this.snappedToSegment.startNode.anomalies;
            node = this.snappedToSegment.startNode;
            anomaly_index = node.anomalies.length - 1;
          } else {
            node = node_data.node;
            anomaly_index = node_data.anomaly_index;
          }
        }

        this.snappedToSegment.dying_road = node.anomalies[anomaly_index].dying_road;
      }

      console.log(node.anomalies);

      if(this.type === AnomalyType[0].type)
        this.snappedToSegment.weight_limit_text.text = node.anomalies[anomaly_index].weight_limit + 'kg';
      else if(this.type === AnomalyType[1].type)
        this.snappedToSegment.dying_road_text.text = node.anomalies[anomaly_index].dying_road + 'm';

    }
  }

  keyDown(event) {
    super.keyDown(event);
  }

  keyUp(event) {
    var self = this;
    if(self.snappedToSegment) {
      // with other anomalies it'll probably be necessary to check both directions
      // weight limit is updated with number keys
      if(this.keyWasDown && self.snappedToSegment.startNode.anomalies.length !== 0) {
        var number = parseInt(event.key);
        if(number || number === 0) {
          if (this.snappedToSegment.startNode.anomalies.length === 0 && this.snappedToSegment.endNode.anomalies.length === 0) {
            if(this.type === AnomalyType[0].type) {
              this.snappedToSegment.startNode.anomalies = [{to:this.snappedToSegment.endNode.getId(), weight_limit:1}];
              this.snappedToSegment.weight_limit = 1;
            } else if(this.type === AnomalyType[1].type) {
              this.snappedToSegment.startNode.anomalies = [{to:this.snappedToSegment.endNode.getId(), dying_road:1}];
              this.snappedToSegment.anomalies = this.snappedToSegment.startNode.anomalies;
            }
          } else {
            if(this.type === AnomalyType[0].type) {
              self.snappedToSegment.startNode.anomalies[0].weight_limit =
              self.snappedToSegment.startNode.anomalies[0].weight_limit * 10 + number;
            } else if(this.type === AnomalyType[1].type) {
              this.snappedToSegment.startNode.anomalies[0].dying_road =
              this.snappedToSegment.startNode.anomalies[0].dying_road * 10 + number;
            }

          }
        } else if(event.key === 'Backspace') {
          if(!(self.snappedToSegment.startNode.anomalies.length === 0 && self.snappedToSegment.endNode.anomalies.length === 0)) {
            if(this.type === AnomalyType[0].type) {
              self.snappedToSegment.startNode.anomalies[0].weight_limit =
              Math.floor(self.snappedToSegment.startNode.anomalies[0].weight_limit / 10);
            } else if(this.type === AnomalyType[1].type) {
              this.snappedToSegment.startNode.anomalies[0].dying_road =
              Math.floor(this.snappedToSegment.startNode.anomalies[0].dying_road / 10);
            }

          }
        }

        // refreshing routes to keep ui look responsive
        self.level.refreshRoutes();
      }
    }

    super.keyUp(event);
  }

  activate() {
    super.activate();
    this.stage.addChildAt(this.anomaly_container,2);
  }

  deactivate() {
    super.deactivate();
    this.stage.removeChild(this.anomaly_container);
  }
}
<commit_msg>Significant improvements on anomaly tools stability.<commit_after>import ITool from './itool';
import * as PIXI from 'pixi.js';
import {length, distance} from '../game/helpers';
import Settings from '../game/settings';

export const AnomalyType = [
  { type : 'weightlimit', name : 'weight_limit' },
  { type : 'dying', name : 'dying_road' }
];

export default class AnomalyTool extends ITool {
  constructor(stage, level, type) {
    super(stage);

    this.level = level;

    this.settings = new Settings().anomalies;

    // type of the anomaly tool
    this.type = type;

    this.weight_limit_pointer_sprite = null;
    this.dying_road_pointer_sprite = null;

    if(this.type == AnomalyType[0].type) {
      this.current_pointer_sprite = this.getWeightLimitPointerSprite();
    } else if( this.type == AnomalyType[1].type ) {
      this.current_pointer_sprite = this.getDyingRoadPointerSprite();
    }

    this.pointerContainer.addChild(this.current_pointer_sprite);

    this.anomaly_container = new PIXI.Container();

    this.snappedToSegment = null;
    this.snappingDistance = 35;
  }

  getWeightLimitPointerSprite() {
    if(this.weight_limit_pointer_sprite === null) {
      this.weight_limit_pointer_sprite = new PIXI.Text( 0 + 'kg',this.settings.WEIGHT_LIMIT_TEXT_FONT);

      this.weight_limit_pointer_sprite.anchor.set(0.5, 0.5);
      this.weight_limit_pointer_sprite.scale.set(this.settings.WEIGHT_LIMIT_TEXT_SCALE);
    }

    return this.weight_limit_pointer_sprite;
  }

  getDyingRoadPointerSprite() {
    if(this.dying_road_pointer_sprite === null) {
      this.dying_road_pointer_sprite = new PIXI.Text( 0 + 'm',this.settings.DYING_ROAD_TEXT_FONT);

      this.dying_road_pointer_sprite.anchor.set(0.5);
      this.dying_road_pointer_sprite.scale.set(this.settings.DYING_ROAD_TEXT_SCALE);
    }

    return this.dying_road_pointer_sprite;
  }

  mouseMove(mouseInput) {
    var epos = mouseInput.worldPosition;
    this.pointerContainer.position = epos;

    // do a search for nearest node in snapping distance
    this.snappedToSegment = null;
    this.level.refreshRoutes();

    var segments = this.level.getRouteSegments();
    var segment_amount =  segments.length;

    for (var i = 0; i < segment_amount; ++i) {
      var segment = segments[i];
      var segment_pos = {
        x: (segment.endNode.getPos().x + segment.startNode.getPos().x) / 2,
        y: (segment.endNode.getPos().y + segment.startNode.getPos().y) / 2
      };

      if (distance(mouseInput.worldPosition, segment_pos) < this.snappingDistance) {
        this.pointerContainer.position.set(segment_pos.x, segment_pos.y);
        this.snappedToSegment = segment;
        epos = segment_pos;
        break;
      }
    }

    //toggling visibility of pointer if we have snapped and there is anomaly

    if(this.snappedToSegment !== null
      && (this.nodeHasAnomalyTo(this.snappedToSegment.startNode,this.snappedToSegment.endNode).node
      || this.nodeHasAnomalyTo(this.snappedToSegment.endNode,this.snappedToSegment.startNode).node)) {
      this.snappedToSegment.weight_limit_text.style.fill = 0x00FF00;
      this.snappedToSegment.dying_road_text.style.fill = 0x00FF00;
      this.pointerContainer.visible = false;
    } else {
      this.pointerContainer.visible = true;
    }

  }

  nodeHasAnomalyTo(node,to_node) {
    var rnode = null;
    var anomaly_index = -1;

    if(!node || !to_node)
      return {node:rnode, anomaly_index:anomaly_index};

    for(var i = 0; i < node.anomalies.length; ++i) {

      if(node.anomalies[i].to === to_node.getId()) {
        rnode = node;
        anomaly_index = i;
        break;
      }
    }

    return {node:rnode, anomaly_index:anomaly_index};
  }

  updateAnomalyInfo(segment, anomaly_name) {
    var node = null;
    var anomaly_index = -1;

    if (segment.startNode.anomalies.length === 0 && segment.endNode.anomalies.length === 0) {
      segment.startNode.anomalies.push({to:segment.endNode.getId(), [anomaly_name]:1});
      segment.weight_limit = 1;
      node = segment.startNode;
      anomaly_index = node.anomalies.length - 1;
    } else {
      // grinding through startnode anomalies
      var node_data = this.nodeHasAnomalyTo(segment.startNode,segment.endNode);

      // if node is not still defined trying endnode
      if(!node_data.node) {
        node_data = this.nodeHasAnomalyTo(segment.endNode,segment.startNode);
      }

      // if we have not found connections we just create it
      if(!node_data.node) {
        segment.startNode.anomalies.push({to:segment.endNode.getId(),[anomaly_name]:1});
        segment.weight_limit = 1;
        node = segment.startNode;
        anomaly_index = node.anomalies.length - 1;
      } else {
        node = node_data.node;
        anomaly_index = node_data.anomaly_index;
      }
    }

    return {node:node, anomaly_index:anomaly_index};
  }

  mouseUp(mouseInput) {
    // mouse moved aka moved the viewport so don't do the action
    if (length(mouseInput.absDeltaDuringMouseDown) > 20)
      return;

    if(this.snappedToSegment !== null) {
      // weight limit
      if(this.type === AnomalyType[0].type) {
        var node_data = this.updateAnomalyInfo(this.snappedToSegment, AnomalyType[0].name);

        console.log(node_data);

        this.snappedToSegment.weight_limit = node_data.node.anomalies[node_data.anomaly_index].weight_limit;

      } else if(this.type === AnomalyType[1].type) {
        var node_data = this.updateAnomalyInfo(this.snappedToSegment, AnomalyType[1].name);

        this.snappedToSegment.dying_road = node_data.node.anomalies[node_data.anomaly_index].dying_road;
      }

      console.log(node_data.node.anomalies);

      if(this.type === AnomalyType[0].type)
        this.snappedToSegment.weight_limit_text.text = node_data.node.anomalies[node_data.anomaly_index].weight_limit + 'kg';
      else if(this.type === AnomalyType[1].type)
        this.snappedToSegment.dying_road_text.text = node_data.node.anomalies[node_data.anomaly_index].dying_road + 'm';

    }
  }

  keyDown(event) {
    super.keyDown(event);
  }

  keyUp(event) {
    var self = this;
    if(self.snappedToSegment) {
      // with other anomalies it'll probably be necessary to check both directions
      // weight limit is updated with number keys
      if(this.keyWasDown && self.snappedToSegment.startNode.anomalies.length !== 0) {
        var number = parseInt(event.key);
        if(number || number === 0) {
          if (this.snappedToSegment.startNode.anomalies.length === 0 && this.snappedToSegment.endNode.anomalies.length === 0) {
            if(this.type === AnomalyType[0].type) {
              this.snappedToSegment.startNode.anomalies = [{to:this.snappedToSegment.endNode.getId(), weight_limit:1}];
              this.snappedToSegment.weight_limit = 1;
            } else if(this.type === AnomalyType[1].type) {
              this.snappedToSegment.startNode.anomalies = [{to:this.snappedToSegment.endNode.getId(), dying_road:1}];
              this.snappedToSegment.anomalies = this.snappedToSegment.startNode.anomalies;
            }
          } else {
            if(this.type === AnomalyType[0].type) {
              self.snappedToSegment.startNode.anomalies[0].weight_limit =
              self.snappedToSegment.startNode.anomalies[0].weight_limit * 10 + number;
            } else if(this.type === AnomalyType[1].type) {
              this.snappedToSegment.startNode.anomalies[0].dying_road =
              this.snappedToSegment.startNode.anomalies[0].dying_road * 10 + number;
            }

          }
        } else if(event.key === 'Backspace') {
          if(!(self.snappedToSegment.startNode.anomalies.length === 0 && self.snappedToSegment.endNode.anomalies.length === 0)) {
            if(this.type === AnomalyType[0].type) {
              self.snappedToSegment.startNode.anomalies[0].weight_limit =
              Math.floor(self.snappedToSegment.startNode.anomalies[0].weight_limit / 10);
            } else if(this.type === AnomalyType[1].type) {
              this.snappedToSegment.startNode.anomalies[0].dying_road =
              Math.floor(this.snappedToSegment.startNode.anomalies[0].dying_road / 10);
            }

          }
        }

        // refreshing routes to keep ui look responsive
        self.level.refreshRoutes();
      }
    }

    super.keyUp(event);
  }

  activate() {
    super.activate();
    this.stage.addChildAt(this.anomaly_container,2);
  }

  deactivate() {
    super.deactivate();
    this.stage.removeChild(this.anomaly_container);
  }
}
<|endoftext|>


<commit_before>// helper functions to make all input elements
define([
    "jquery",
    "../core/logger"
], function($, logging) {
    var namespace = "input-change-events",
        log = logging.getLogger(namespace);

    var _ = {
        setup: function($form, pat) {

            if (!$form.is("form,.pat-subform")) {
                log.error("Input change event handler can only be set on forms.");
                return;
            }

            if (!pat) {
                log.error("The name of the calling pattern has to be set.");
                return;
            }

            // list of patterns that installed input-change-event handlers
            var patterns = $form.data(namespace) || [];
            log.debug("setup handlers for " + pat);

            if (!patterns.length) {
                log.debug("installing handlers");

                $form.find(":input").each(function() {
                    var $el = $(this),
                        isText = $el.is("input:text, input[type=search], textarea");

                    if (isText) {
                        if ("oninput" in window) {
                            $el.on("input." + namespace, function() {
                                log.debug('translating input');
                                $el.trigger("input-change");
                            });
                        } else {
                            // this is the legacy code path for IE8
                            // Work around buggy placeholder polyfill.
                            if ($el.attr('placeholder')) {
                                $el.on("keyup." + namespace, function() {
                                    log.debug('translating keyup');
                                    $el.trigger("input-change");
                                });
                            } else {
                                $el.on("propertychange." + namespace, function(ev) {
                                    if (ev.originalEvent.propertyName === 'value') {
                                        log.debug('translating propertychange');
                                        $el.trigger("input-change");
                                    }
                                });
                            }
                        }
                    } else {
                        $el.on("change." + namespace, function() {
                            log.debug('translating change');
                            $el.trigger("input-change");
                        });
                    }
                });
            }

            if (patterns.indexOf(pat) === -1) {
                patterns.push(pat);
                $form.data(namespace, patterns);
            }
        },

        remove: function($form, pat) {
            var patterns = $form.data(namespace) || [];
            if (patterns.indexOf(pat) === -1) {
                log.warn("input-change-events were never installed for " + pat);
            } else {
                patterns = patterns.filter(function(e){return e!==pat;});
                if (patterns.length) {
                    $form.data(namespace, patterns);
                } else {
                    log.debug('remove handlers');
                    $form.removeData(namespace);
                    $form.find(':input').off('.' + namespace);
                }
            }
        }
    };

    return _;

});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>Trigger missing input-defocus event.<commit_after>// helper functions to make all input elements
define([
    "jquery",
    "../core/logger"
], function($, logging) {
    var namespace = "input-change-events",
        log = logging.getLogger(namespace);

    var _ = {
        setup: function($form, pat) {

            if (!$form.is("form,.pat-subform")) {
                log.error("Input change event handler can only be set on forms.");
                return;
            }

            if (!pat) {
                log.error("The name of the calling pattern has to be set.");
                return;
            }

            // list of patterns that installed input-change-event handlers
            var patterns = $form.data(namespace) || [];
            log.debug("setup handlers for " + pat);

            if (!patterns.length) {
                log.debug("installing handlers");

                $form.find(":input").each(function() {
                    var $el = $(this),
                        isText = $el.is("input:text, input[type=search], textarea");

                    if (isText) {
                        if ("oninput" in window) {
                            $el.on("input." + namespace, function() {
                                log.debug('translating input');
                                $el.trigger("input-change");
                            });
                        } else {
                            // this is the legacy code path for IE8
                            // Work around buggy placeholder polyfill.
                            if ($el.attr('placeholder')) {
                                $el.on("keyup." + namespace, function() {
                                    log.debug('translating keyup');
                                    $el.trigger("input-change");
                                });
                            } else {
                                $el.on("propertychange." + namespace, function(ev) {
                                    if (ev.originalEvent.propertyName === 'value') {
                                        log.debug('translating propertychange');
                                        $el.trigger("input-change");
                                    }
                                });
                            }
                        }
                    } else {
                        $el.on("change." + namespace, function() {
                            log.debug('translating change');
                            $el.trigger("input-change");
                        });
                    }

                    $el.on("blur", function() {
                        $el.trigger("input-defocus");
                    });
                });
            }

            if (patterns.indexOf(pat) === -1) {
                patterns.push(pat);
                $form.data(namespace, patterns);
            }
        },

        remove: function($form, pat) {
            var patterns = $form.data(namespace) || [];
            if (patterns.indexOf(pat) === -1) {
                log.warn("input-change-events were never installed for " + pat);
            } else {
                patterns = patterns.filter(function(e){return e!==pat;});
                if (patterns.length) {
                    $form.data(namespace, patterns);
                } else {
                    log.debug('remove handlers');
                    $form.removeData(namespace);
                    $form.find(':input').off('.' + namespace);
                }
            }
        }
    };

    return _;

});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>


<commit_before>  // ==UserScript==
  // @name        KA Crowdin Translation Tools
  // @namespace   kadeutsch.org/kacrowdintools
  // @include     https://crowdin.com/translate/khanacademy/*
  // @include     https://translate.khanacademy.org/translate/*
  // @description Various keyboard shortcuts to make KA translation on crowdin a little bit easier
  // @version     0.5.2
  // @grant       none
  // @author      Alain Schaefer & Uli Köhler
  // @updateURL   https://gist.githubusercontent.com/alani1/a62cd694ba35ed11744e/raw/KATranslationTools.user.js
  // ==/UserScript==

  /*
   Alt + A : Show all excercises (only on translate.khanacadey.org)
   Alt + N : Move to next exercise (only on translate.khanacadey.org)
   Alt + P : Open all new Window with Strings in proofread modus on crowdin (only on translate.khanacadey.org)
   Alt + U : Replace all formulas with formulas from englisch original (works on crowin.net and translate.khanacademy.org)
   Alt + I : replace Image URLs and fix common german Bing-Translation Errors (works on crowin.net and translate.khanacademy.org)
   Alt + W : Auto-fix coordinates. Works only for coordinates WITHOUT operators.
   Alt + Q : Other fixes. Currently: Convert $\\$123$ to $123{\,}€$ ; Convert simple numbers with dot to comma
   Alt + L : Show images
   */

  /**
   * Get the text value in the textbox.
   * For debugging purposes ONLY. The information where to replace
   * the value is lost!!
   */
  function getTxtboxValue(regex, replacement) {
      var txtBox = document.getElementById('translation');
      return (txtBox.innerHTML || txtBox.value);
  }

  function replacePattern(expr,expr2){

      //find the propper Crowdin HTML Elements
      var myDoc = document.getElementById('source_phrase_container');
      var txtBox = document.getElementById('translation');

      //copy the HTML values
      var sourceStr = myDoc.innerText;
      var txtBoxValue = (txtBox.innerHTML || txtBox.value);

      var valueIsInValueProperty = (txtBox.innerHTML == "");

      var result;
      var newTextBoxValue = txtBoxValue;

      //replace every occurence of pattern with corresponding occurence in sourcestring e.g. 1st with 1st, 2nd with 2nd
      while (( result = expr.exec(sourceStr)) !== null  ) {
          txtBoxResult = expr2.exec(txtBoxValue);
          //console.log('replacing ' + txtBoxResult[0] + " with " + result[0]);
          newTextBoxValue = newTextBoxValue.replace(txtBoxResult[0], result[0]);
      }
      //Set new value
      if(valueIsInValueProperty) {txtBox.value = newTextBoxValue;}
      else {txtBox.innerHTML = newTextBoxValue;}
  }

  /**
   * Simple replace without looking at original (untranslated) string.
   */
  function simpleReplaceInTxtbox(regex, replacement) {
      var txtBox = document.getElementById('translation');
      var valueIsInValueProperty = (txtBox.innerHTML == "");
      var txtBoxValue = (txtBox.innerHTML || txtBox.value);
      var newTxtBoxValue = txtBoxValue.replace(regex, replacement);
      //Set new value
      if(valueIsInValueProperty) {txtBox.value = newTxtBoxValue;}
      else {txtBox.innerHTML = newTxtBoxValue;}
  }

  var imagesShown = false;

  /**
   * Simple replace without looking at original (untranslated) string.
   */
  function showImages() {
    console.log("showimag");
      var txtBox = document.getElementById('translation');
      var myDoc = document.getElementById('source_phrase_container');
      var sourceStr = myDoc.innerText;
      var expr =  /!\[\]\(([^\)]+)\)/g;
      while (( result = expr.exec(sourceStr)) !== null  ) {
        var tag = "";
        console.log(tag);
          $("#translation_text_container").prepend(tag);
      }
  }

  function showAllQuestions() {
      var hint = $('#hint');
      while (!hint.is(":disabled")) {
          hint.click();
      }
  }

  /**
   * Experimental opening of URL in new tab.
   * DOES NOT WORK for Alt+P due to security restrictions.
   */
  function chromeOpenURL(url) {
    var yourCustomJavaScriptCode = 'var win = window.open(\"' + url + '\", "_blank"); win.focus();';
    var script = document.createElement('script');
    var code = document.createTextNode('(function() {' + yourCustomJavaScriptCode + '})();');
    script.appendChild(code);
    (document.body || document.head).appendChild(script);

  }
  // TODO check on which host we are running, Alt+P and Alt+A only work on translate.khanacademy.org, the others on crowdin.com
  function key_event(e) {
    var txtBox = document.getElementById('translation');

    // Alt+P open this exercise in the proofread modus, only works if you have proofread permission on crowdin
    if (e.altKey && e.keyCode == 80) {
        // extract the exercise name e.g. exercises=rounding-to-the-nearest-ten-or-hundred#xe9e1a3b9f44d9837
        var expr =  /.+=(.+)#.+/g
        var url = window.location.href;
        var result = expr.exec(url);

        // todo find out the language which is currently used on translate.khanacademy.org
        var proof = "https://crowdin.com/proofread/khanacademy/all/enus-de#q=e/";
        var url = proof + result[1]
        console.log(url);
        window.open(url,'_blank');
        if(chrome) {
          window.location.href = url;
        }
    }

    // Alt+A for showing all exercise questions
    if (e.altKey && e.keyCode == 65) {
      showAllQuestions();
    }

    // Alt+N to move to the next exercise on translate.khanacademy.org
    if (e.altKey && e.keyCode == 78) {
        var item = $("li:has(a.active)");
        item.parent().children("li:eq("+(item.index()+1)+")").children("a").click();
        showAllQuestions();
    }

    // match Alt+O replace coordinates in the form of ( 4.5 , 5.6 ) with ( 4.5 | 5.6 ), white spaces are not trimmed
    if (e.altKey && e.keyCode == 79) {
        txtBox.innerHTML = txtBox.innerHTML.replace( /\(([-+]?[0-9]*\.?[0-9]+),( ?[-+]?[0-9]*\.?[0-9]+)\)/g, '($1|$2)' );
    }
    // match Alt+I replace KA image urls & fix the Bing ImgURL error
    if (e.altKey && e.keyCode == 73) {

      //Fix the Bing ImgURL error
      txtBox.innerHTML = txtBox.innerHTML.replace('![] (','![](');
      txtBox.innerHTML = txtBox.innerHTML.replace('! [] (','![](');
      txtBox.innerHTML = txtBox.innerHTML.replace('Interaktive Grafik','interactive-graph');
      txtBox.innerHTML = txtBox.innerHTML.replace(/Radio/g,'radio');
      txtBox.innerHTML = txtBox.innerHTML.replace(/Eingabe-Zahl/,'input-number');
      txtBox.innerHTML = txtBox.innerHTML.replace(/Eingabe-Nummer/g,'input-number');
      txtBox.innerHTML = txtBox.innerHTML.replace(/numerische Eingabe/g,'numeric-input');
      txtBox.innerHTML = txtBox.innerHTML.replace(/numerische-Eingang/g,'numeric-input');
      txtBox.innerHTML = txtBox.innerHTML.replace(/☃ Bild/g,'☃ image');
      txtBox.innerHTML = txtBox.innerHTML.replace('**How','**Wie');
      txtBox.innerHTML = txtBox.innerHTML.replace('**What','**Was');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ ones}}/g,'\\text{ Einer}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ one}}/g,'\\text{ Einer}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ tens}}/g,'\\text{ Zehner}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ ten}}/g,'\\text{ Zehner}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ hundred}}/g,'\\text{ Hunderter}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ hundreds}}/g,'\\text{ Hunderter}}');

      //Not sure what alanis stuff above should do, but it doesn't work. This one does.
      simpleReplaceInTxtbox(/!\s+\[\]\s+\(/g, "![](");

      // on regex for english, and second for translation string
      var expr =  /!\[\]\((.+?)\)/g
      var expr2 =  /!\[\]\((.+?)\)/g

      replacePattern(expr,expr2); //causes exception.

    }
    // Alt+L: Show images in text
    if (e.altKey && e.keyCode == 76) {
      if(imagesShown) {
        $(".greasemonkey-image").remove();
        imagesShown = false;
      } else {
        showImages();
        imagesShown = true;
      }
    }
    // Replace math formulas contained in Dollar signs, Alt+U
    if (e.altKey && e.keyCode == 85) {
        // TODO: need to correctly handle escpade Dollar signs e.g. \$ should not be machted as start or end string works in Python and PHP \$.+?(?<!\\)\$ but no in javascript
          var expr = /(\$.+?\$)/g
          var expr2 = /(\$.+?\$)/g
          replacePattern(expr,expr2);
    }
    // Fix coordinates, Alt+W
    if (e.altKey && e.keyCode == 87) {
        var expr = /\$\((-?\d+(([\.,]|\{,\})\d+)?|-?[a-z])\s*[,;|]\s*(-?\d+(([\.,]|\{,\})\d+)?| -?[a-z])\)\$/g;
        simpleReplaceInTxtbox(expr, "\$($1{\\,}|{\\,}$4)\$");
    }
    // Other fixes, Alt+Q
    if (e.altKey && e.keyCode == 81) {
        //Dollary
        simpleReplaceInTxtbox(/\$\\\\\$(-?\d+([.,]\d+)?)\$/g, "$$$1{\\,}€\$");
        //Decimal dot to comma
        simpleReplaceInTxtbox(/(-?\d+)\.(-?\d+)/g, "$1{,}$2");
        //In number: Need {\,} instead of {\ }
        simpleReplaceInTxtbox(/(\d+)\{\\ \}(\d+)/g, "$1{\\,}$2");
        //In number: Need percentage
        simpleReplaceInTxtbox(/\\*\s*%\s*\$/g, "{\\,}\\%$$");
        //daß -> dass
        simpleReplaceInTxtbox(/daß/g, "dass");
        //daß -> dass
        simpleReplaceInTxtbox(/\.\s*\*\s+\*/g, ".**");
        //x-intercept / y-intercept
        simpleReplaceInTxtbox(/\$x\$-intercept/g, "Schnittpunkt mit der $$x$$-Achse");
        simpleReplaceInTxtbox(/\$y\$-intercept/g, "Schnittpunkt mit der $$y$$-Achse");

    }
  }
  document.addEventListener('keydown', key_event, true);<commit_msg>Added 40€ -> 40{\,}€ misc fix<commit_after>  // ==UserScript==
  // @name        KA Crowdin Translation Tools
  // @namespace   kadeutsch.org/kacrowdintools
  // @include     https://crowdin.com/translate/khanacademy/*
  // @include     https://translate.khanacademy.org/translate/*
  // @description Various keyboard shortcuts to make KA translation on crowdin a little bit easier
  // @version     0.5.3
  // @grant       none
  // @author      Alain Schaefer & Uli Köhler
  // @updateURL   https://gist.githubusercontent.com/alani1/a62cd694ba35ed11744e/raw/KATranslationTools.user.js
  // ==/UserScript==

  /*
   Alt + A : Show all excercises (only on translate.khanacadey.org)
   Alt + N : Move to next exercise (only on translate.khanacadey.org)
   Alt + P : Open all new Window with Strings in proofread modus on crowdin (only on translate.khanacadey.org)
   Alt + U : Replace all formulas with formulas from englisch original (works on crowin.net and translate.khanacademy.org)
   Alt + I : replace Image URLs and fix common german Bing-Translation Errors (works on crowin.net and translate.khanacademy.org)
   Alt + W : Auto-fix coordinates. Works only for coordinates WITHOUT operators.
   Alt + Q : Other fixes. Currently: Convert $\\$123$ to $123{\,}€$ ; Convert simple numbers with dot to comma
   Alt + L : Show images
   */

  /**
   * Get the text value in the textbox.
   * For debugging purposes ONLY. The information where to replace
   * the value is lost!!
   */
  function getTxtboxValue(regex, replacement) {
      var txtBox = document.getElementById('translation');
      return (txtBox.innerHTML || txtBox.value);
  }

  function replacePattern(expr,expr2){

      //find the propper Crowdin HTML Elements
      var myDoc = document.getElementById('source_phrase_container');
      var txtBox = document.getElementById('translation');

      //copy the HTML values
      var sourceStr = myDoc.innerText;
      var txtBoxValue = (txtBox.innerHTML || txtBox.value);

      var valueIsInValueProperty = (txtBox.innerHTML == "");

      var result;
      var newTextBoxValue = txtBoxValue;

      //replace every occurence of pattern with corresponding occurence in sourcestring e.g. 1st with 1st, 2nd with 2nd
      while (( result = expr.exec(sourceStr)) !== null  ) {
          txtBoxResult = expr2.exec(txtBoxValue);
          //console.log('replacing ' + txtBoxResult[0] + " with " + result[0]);
          newTextBoxValue = newTextBoxValue.replace(txtBoxResult[0], result[0]);
      }
      //Set new value
      if(valueIsInValueProperty) {txtBox.value = newTextBoxValue;}
      else {txtBox.innerHTML = newTextBoxValue;}
  }

  /**
   * Simple replace without looking at original (untranslated) string.
   */
  function simpleReplaceInTxtbox(regex, replacement) {
      var txtBox = document.getElementById('translation');
      var valueIsInValueProperty = (txtBox.innerHTML == "");
      var txtBoxValue = (txtBox.innerHTML || txtBox.value);
      var newTxtBoxValue = txtBoxValue.replace(regex, replacement);
      //Set new value
      if(valueIsInValueProperty) {txtBox.value = newTxtBoxValue;}
      else {txtBox.innerHTML = newTxtBoxValue;}
  }

  var imagesShown = false;

  /**
   * Simple replace without looking at original (untranslated) string.
   */
  function showImages() {
    console.log("showimag");
      var txtBox = document.getElementById('translation');
      var myDoc = document.getElementById('source_phrase_container');
      var sourceStr = myDoc.innerText;
      var expr =  /!\[\]\(([^\)]+)\)/g;
      while (( result = expr.exec(sourceStr)) !== null  ) {
        var tag = "";
        console.log(tag);
          $("#translation_text_container").prepend(tag);
      }
  }

  function showAllQuestions() {
      var hint = $('#hint');
      while (!hint.is(":disabled")) {
          hint.click();
      }
  }

  /**
   * Experimental opening of URL in new tab.
   * DOES NOT WORK for Alt+P due to security restrictions.
   */
  function chromeOpenURL(url) {
    var yourCustomJavaScriptCode = 'var win = window.open(\"' + url + '\", "_blank"); win.focus();';
    var script = document.createElement('script');
    var code = document.createTextNode('(function() {' + yourCustomJavaScriptCode + '})();');
    script.appendChild(code);
    (document.body || document.head).appendChild(script);

  }
  // TODO check on which host we are running, Alt+P and Alt+A only work on translate.khanacademy.org, the others on crowdin.com
  function key_event(e) {
    var txtBox = document.getElementById('translation');

    // Alt+P open this exercise in the proofread modus, only works if you have proofread permission on crowdin
    if (e.altKey && e.keyCode == 80) {
        // extract the exercise name e.g. exercises=rounding-to-the-nearest-ten-or-hundred#xe9e1a3b9f44d9837
        var expr =  /.+=(.+)#.+/g
        var url = window.location.href;
        var result = expr.exec(url);

        // todo find out the language which is currently used on translate.khanacademy.org
        var proof = "https://crowdin.com/proofread/khanacademy/all/enus-de#q=e/";
        var url = proof + result[1]
        console.log(url);
        window.open(url,'_blank');
        if(chrome) {
          window.location.href = url;
        }
    }

    // Alt+A for showing all exercise questions
    if (e.altKey && e.keyCode == 65) {
      showAllQuestions();
    }

    // Alt+N to move to the next exercise on translate.khanacademy.org
    if (e.altKey && e.keyCode == 78) {
        var item = $("li:has(a.active)");
        item.parent().children("li:eq("+(item.index()+1)+")").children("a").click();
        showAllQuestions();
    }

    // match Alt+O replace coordinates in the form of ( 4.5 , 5.6 ) with ( 4.5 | 5.6 ), white spaces are not trimmed
    if (e.altKey && e.keyCode == 79) {
        txtBox.innerHTML = txtBox.innerHTML.replace( /\(([-+]?[0-9]*\.?[0-9]+),( ?[-+]?[0-9]*\.?[0-9]+)\)/g, '($1|$2)' );
    }
    // match Alt+I replace KA image urls & fix the Bing ImgURL error
    if (e.altKey && e.keyCode == 73) {

      //Fix the Bing ImgURL error
      txtBox.innerHTML = txtBox.innerHTML.replace('![] (','![](');
      txtBox.innerHTML = txtBox.innerHTML.replace('! [] (','![](');
      txtBox.innerHTML = txtBox.innerHTML.replace('Interaktive Grafik','interactive-graph');
      txtBox.innerHTML = txtBox.innerHTML.replace(/Radio/g,'radio');
      txtBox.innerHTML = txtBox.innerHTML.replace(/Eingabe-Zahl/,'input-number');
      txtBox.innerHTML = txtBox.innerHTML.replace(/Eingabe-Nummer/g,'input-number');
      txtBox.innerHTML = txtBox.innerHTML.replace(/numerische Eingabe/g,'numeric-input');
      txtBox.innerHTML = txtBox.innerHTML.replace(/numerische-Eingang/g,'numeric-input');
      txtBox.innerHTML = txtBox.innerHTML.replace(/☃ Bild/g,'☃ image');
      txtBox.innerHTML = txtBox.innerHTML.replace('**How','**Wie');
      txtBox.innerHTML = txtBox.innerHTML.replace('**What','**Was');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ ones}}/g,'\\text{ Einer}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ one}}/g,'\\text{ Einer}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ tens}}/g,'\\text{ Zehner}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ ten}}/g,'\\text{ Zehner}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ hundred}}/g,'\\text{ Hunderter}}');
      txtBox.innerHTML = txtBox.textContent.replace(/\\text{ hundreds}}/g,'\\text{ Hunderter}}');

      //Not sure what alanis stuff above should do, but it doesn't work. This one does.
      simpleReplaceInTxtbox(/!\s+\[\]\s+\(/g, "![](");

      // on regex for english, and second for translation string
      var expr =  /!\[\]\((.+?)\)/g
      var expr2 =  /!\[\]\((.+?)\)/g

      replacePattern(expr,expr2); //causes exception.

    }
    // Alt+L: Show images in text
    if (e.altKey && e.keyCode == 76) {
      if(imagesShown) {
        $(".greasemonkey-image").remove();
        imagesShown = false;
      } else {
        showImages();
        imagesShown = true;
      }
    }
    // Replace math formulas contained in Dollar signs, Alt+U
    if (e.altKey && e.keyCode == 85) {
        // TODO: need to correctly handle escpade Dollar signs e.g. \$ should not be machted as start or end string works in Python and PHP \$.+?(?<!\\)\$ but no in javascript
          var expr = /(\$.+?\$)/g
          var expr2 = /(\$.+?\$)/g
          replacePattern(expr,expr2);
    }
    // Fix coordinates, Alt+W
    if (e.altKey && e.keyCode == 87) {
        var expr = /\$\((-?\d+(([\.,]|\{,\})\d+)?|-?[a-z])\s*[,;|]\s*(-?\d+(([\.,]|\{,\})\d+)?| -?[a-z])\)\$/g;
        simpleReplaceInTxtbox(expr, "\$($1{\\,}|{\\,}$4)\$");
    }
    // Other fixes, Alt+Q
    if (e.altKey && e.keyCode == 81) {
        //Dollary
        simpleReplaceInTxtbox(/\$\\\\\$(-?\d+([.,]\d+)?)\$/g, "$$$1{\\,}€\$");
        //Decimal dot to comma
        simpleReplaceInTxtbox(/(-?\d+)\.(-?\d+)/g, "$1{,}$2");
        //In number: Need {\,} instead of {\ }
        simpleReplaceInTxtbox(/(\d+)\{\\ \}(\d+)/g, "$1{\\,}$2");
        //In number: Need percentage
        simpleReplaceInTxtbox(/\\*\s*%\s*\$/g, "{\\,}\\%$$");
        //daß -> dass
        simpleReplaceInTxtbox(/daß/g, "dass");
        //daß -> dass
        simpleReplaceInTxtbox(/\.\s*\*\s+\*/g, ".**");
        //40€ -> 40{\,}€ (simple cases)
        simpleReplaceInTxtbox(/\$(\d+)€/g, "$$$1{\\,}€");
        simpleReplaceInTxtbox(/(\d+)€$/g, "$1{\\,}€$$");
        //x-intercept / y-intercept
        simpleReplaceInTxtbox(/\$x\$-intercept/g, "Schnittpunkt mit der $$x$$-Achse");
        simpleReplaceInTxtbox(/\$y\$-intercept/g, "Schnittpunkt mit der $$y$$-Achse");

    }
  }
  document.addEventListener('keydown', key_event, true);<|endoftext|>


<commit_before>// fetches all required resources from url, builds an Object and returns
// a promise
function processUrlJob(urlJob) {
  // XXX write this part
  console.log('processUrlJob - ' + urlJob);
  return Promise.resolve({
    hasProbe1: false,
    hasProbe2: false,
    hasProbe3: true,
  });
}

export default {
  processUrlJob,
};

const test = {
};

export { test };
<commit_msg>Remove unused `test` export from urlJobProcessor<commit_after>// fetches all required resources from url, builds an Object and returns
// a promise
function processUrlJob(urlJob) {
  // XXX write this part
  console.log('processUrlJob - ' + urlJob);
  return Promise.resolve({
    hasProbe1: false,
    hasProbe2: false,
    hasProbe3: true,
  });
}

export default {
  processUrlJob,
};
<|endoftext|>