<commit_before>var REVIEW_COLS = 2;

Template.SingleApp.onDestroyed(function() {
  AppMarket.setPageTitlePrefix('');
});

Template.SingleApp.onCreated(function() {
  var tmp = this;
  tmp.appId = FlowRouter.getParam('appId');
  tmp.ready = new ReactiveVar(false);
  tmp.readMore = new ReactiveVar(false);
  tmp.autorun(function () {
    try {
      if (AppMarket.ensureDetailsFetched(tmp.appId)) {
        tmp.ready.set(true);
        var app = Apps.findOne(tmp.appId);
        AppMarket.setPageTitlePrefix(app.name);
        if (!app.screenshots.length) tmp.readMore.set(true);
      }
    } catch (err) {
      if (err.toString().startsWith("Error: failed [404]")) {
        BlazeLayout.render('MasterLayout', {mainSection: 'NotFound'});
        return;
      }
      console.error(err);
      // TODO: This doesn't work as there is no `errorModal` template.
      AntiModals.overlay('errorModal', {data: {err: 'There was an error loading app data from the server'}});
    }
  });

  tmp.flagApp = new ReactiveVar(!!FlowRouter.current().queryParams.flag);
  tmp.writeReview = new ReactiveVar(false);
  tmp.myReview = new ReactiveVar({});
  tmp.reviewExistsServerSide = new ReactiveVar(false);
  tmp.reviewValidator = new SimpleSchema({
    rating: {
      type: Number,
      min: 0,
      max: 3
    },
    text: {
      type: String,
      max: 500,
      optional: true
    }
  }).namedContext();
  tmp.reviewValid = new ReactiveVar(false);
  tmp.validateReview = function() {
    var review = tmp.myReview.get();
    var valid = tmp.reviewValidator.validate(review);
    tmp.reviewValid.set(valid);
    return valid;
  };

  // Load existing review of this app (if it exists)
  tmp.subscribe('reviews', tmp.appId, function() {
    var myReview = Reviews.findOne({
      userId: Meteor.userId(),
      appId: tmp.appId
    });
    if (myReview) {
      tmp.myReview.set(_.pick(myReview, ['text', 'rating']));
      tmp.reviewExistsServerSide.set(true);
    }
  });

});

Template.SingleApp.onRendered(function() {

  var tmp = this;

  if (FlowRouter.getQueryParam('rateApp') && FlowRouter.getQueryParam('ratingNumber')) {

    var myReview = tmp.get('myReview').get();
    myReview.rating = parseInt(FlowRouter.getQueryParam('ratingNumber'), 10);
    tmp.get('myReview').set(myReview);
    tmp.get('validateReview').call();
    tmp.get('writeReview').set(true);
    // I cannot get this to work without a timeout, despite apparently waiting
    // for everyhting else to be ready
    $(document).ready(function() {
      Tracker.afterFlush(function() {
        Meteor.setTimeout(function() {
          var reviewsSection = $('.reviews-section');
          reviewsSection.length && $(window).scrollTo(reviewsSection[0]);
          $('[data-field="review-text"]').focus();
        }, 50);
      });
    });
  }

});

Template.SingleApp.helpers({

  app: function() {

    return Apps.findOne(Template.instance().appId);

  },

  installed: function() {

    return false;
    // return (Meteor.user() && this._id in Meteor.user().installedApps);

  },

  hasSandstormHost: function () {
    return AppMarket.hasSandstormHost();
  },

  getDescription: function() {

    return Template.instance().readMore.get() ? this.description : s.prune(this.description, 1200);

  },

  extendedDescription: function() {

    return this.description && this.description.length > 1200;

  },

  flagApp: function() {

    return Template.instance().flagApp.get();

  },

  flagged: function() {

    Template.instance().flagApp.dep.depend();
    var flags = amplify.store('sandstormAppFlags');
    return flags && this._id in flags;

  },

  flagDetails: function() {

    var flags = amplify.store('sandstormAppFlags');
    return flags && flags[this._id];
  },

  readMore: function() {

    return Template.instance().readMore.get();

  },

  writeReview: function() {
    return Template.instance().writeReview.get();

  },

  myReview: function() {

    return Template.instance().myReview.get();

  },

  shouldShowEditReviewLink: function() {
    return (Template.instance().reviewExistsServerSide.get() &&
            !Template.instance().writeReview.get());
  },

  reviewExistsServerSide: function() {
    return Template.instance().reviewExistsServerSide.get();
  },

  reviewValid: function() {

    return Template.instance().reviewValid.get();

  },

  reviews: function() {

    return Reviews.find({appId: Template.instance().appId}).fetch();

  },

  renderedChangelog: function() {

    return this.changelog && htmlTruncate(marked(this.changelog), 200);

  },

  amazingCount: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return reviewData.ratings.amazing;
  },

  amazingPercent: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return (100 * reviewData.ratings.amazing / reviewData.ratingsCount);
  },

  goodCount: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return reviewData.ratings.jobDone;
  },

  goodPercent: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return (100 * reviewData.ratings.jobDone / reviewData.ratingsCount);
  },

  notSoGoodCount: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return reviewData.ratings.didntLike;
  },

  notSoGoodPercent: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return (100 * reviewData.ratings.didntLike / reviewData.ratingsCount);
  },

  needsFixCount: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return reviewData.ratings.broken;
  },

  needsFixPercent: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return (100 * reviewData.ratings.broken / reviewData.ratingsCount);
  },

});

Template.SingleApp.events({

  'click [data-action="read-more"]': function(evt, tmp) {

    tmp.readMore.set(!tmp.readMore.get());

  },

  'click [data-action="read-more"]': function(evt, tmp) {

    tmp.readMore.set(!tmp.readMore.get());

  },

  'input [data-field="review-text"]': function(evt, tmp) {

    var review = tmp.myReview.get();
    review.text = $(evt.currentTarget).val();
    tmp.myReview.set(review);
    tmp.validateReview();

  },

  'click [data-action="discard-review"]': function(evt, tmp) {

    tmp.myReview.set({});
    Meteor.call('user/discardReview', tmp.appId, AppMarket.redirectOrErrorCallback(null, function() {
      tmp.writeReview.set(false);
    }));

  },

  'click [data-action="cancel-review"]': function(evt, tmp) {
    // Delete no local data, but set `writeReview` to false.
    tmp.writeReview.set(false);
  },

  'click [data-action="submit-review"]': function(evt, tmp) {

    if (tmp.reviewValid.get()) {
      Meteor.call('user/reviewApp', tmp.appId, tmp.myReview.get(), AppMarket.redirectOrErrorCallback(null, function() {
        tmp.writeReview.set(false);
      }));
    }

  },

  'mouseover [data-action="review-tooltip"]': function(evt, tmp) {

    if (!tmp.reviewValid.get()) {
      Tooltips.setClasses(['invalid']);
      Tooltips.show(tmp.$('.my-rating-box')[0], 'You need to choose a rating', 's');
      Tooltips.hideDelay(3000, 500);
    }

  },

  'click [data-action="edit-review"]': function(evt, tmp) {
    tmp.writeReview.set(true);
  },

  'click [data-action="flag-app"]': function(evt, tmp) {

    if (Meteor.userId()) tmp.get('flagApp').set(!tmp.get('flagApp').get());
    else {
      AppMarket.loginRedirect = FlowRouter.current().path;
      FlowRouter.go('login');
    }

  },

});

Template.flagBox.onRendered(function() {

  if (this.data && this.data.cat) {
    if (!this.$('input[type="radio"][data-category="' + this.data.cat + '"]').length) {
      this.$('input[type="radio"][data-category="other"]').prop("checked", true);
      this.$('[data-field="flag-other"]').val(this.data.cat);
    }
    else this.$('input[type="radio"][data-category="' + this.data.cat + '"]').prop("checked", true);
  }

});

Template.flagBox.events({

  'click [data-action="flag-app"]': function(evt, tmp) {

    if (Meteor.userId()) tmp.get('flagApp').set(!tmp.get('flagApp').get());

  }

});

Template.carousel.onRendered(function() {

  var tmp = this;

  tmp.$(".owl-carousel").owlCarousel({
    items: Meteor.Device.isPhone() ? 1 : 2,
    loop: true,
    mouseDrag: false,
    nav: false,
    navRewind: false,
    navText: [''next'',''prev''],
    dots: false,
    onInitialized: function() {
      tmp.$('[data-action="image-lightbox"]').each(function() {
        var $el = $(this);
        Meteor.defer(function() {
          $el.magnificPopup({
            items: {
              src: $el.data('src')
            },
            type: 'image',
            disableOn: 400,
            zoom: {
              enabled: true,
              duration: 300,
              easing: 'ease-in-out',
              opener: function() {
                return $el;
              }
            }
          });
        });
      });
    }
  });

});

Template.carousel.helpers({

  extraScreenshots: function() {
    return this.images && this.images.length - (Meteor.Device.isPhone() ? 1 : 2) > 0;
  }

});

Template.carousel.events({

  'click [data-action="carousel-prev"]:not(.disabled)': function (evt, tmp) {

    tmp.$('.owl-carousel').trigger('prev.owl.carousel');

  },

  'click [data-action="carousel-next"]:not(.disabled)': function (evt, tmp) {

    tmp.$('.owl-carousel').trigger('next.owl.carousel');

  },

  'click [data-action="image-lightbox"]': function(evt, tmp) {

    tmp.$(evt.currentTarget).magnificPopup({
        items: {
          src: tmp.$(evt.currentTarget).data('src')
        },
        type: 'image',
        disableOn: 400,
        zoom: {
          enabled: true,
          duration: 300,
          easing: 'ease-in-out',
          opener: function() {
            return tmp.$(evt.currentTarget);
          }
        }
    });

  }

});

Template.reviewFrame.onRendered(function() {

  var tmp = this;

  tmp.autorun(function() {
    if (Template.currentData()) Tracker.afterFlush(function() {
      tmp.$(".owl-carousel").owlCarousel({
        items: REVIEW_COLS,
        loop: true,
        mouseDrag: false,
        nav: false,
        navRewind: false,
        dots: false
      });
    });

  });

});

Template.reviewFrame.helpers({
  reviewGroups: function() {

    if (!this.reviews) return [];

    var size = Math.ceil(this.reviews.length / REVIEW_COLS);

    var res = _.groupBy(this.reviews, function(reviews, i) {
        return Math.floor(i / size);
    });
    return _.values(res);

  }
});

Template.reviewFrame.events({

  'click [data-action="reviews-prev"]': function (evt, tmp) {

    tmp.$('.owl-carousel').trigger('prev.owl.carousel');

  },

  'click [data-action="reviews-next"]': function (evt, tmp) {

    tmp.$('.owl-carousel').trigger('next.owl.carousel');

  }

});

Template.flagBox.events({

    'click [data-action="flag-app"]': function(evt, tmp) {

      if (Meteor.userId()) tmp.get('flagApp').set(!tmp.get('flagApp').get());
      else {
        AppMarket.loginRedirect = FlowRouter.current().path;
        FlowRouter.go('login');
      }

    },

    'click [data-action="submit-flag-app"]': function(evt, tmp) {

      var cat = tmp.$('input[name="flag-reason"]:checked').data('category'),
          additional = tmp.$('[data-field="additional-info"]').text(),
          flag;

      if (!cat) return;
      else if (cat === 'other') cat = tmp.$('[data-field="flag-other"]').val() || cat;

      flag = {
        cat: cat,
        additional: additional
      };
      var localFlags = amplify.store('sandstormAppFlags') || {};
      localFlags[tmp.get('appId')] = flag;

      $.ajax({
        method: 'POST',
        url: Meteor.settings && Meteor.settings.public && Meteor.settings.public.FLAG,
        data: flag,
        success: function() {
          amplify.store('sandstormAppFlags', localFlags);
          return tmp.get('flagApp').set(false);
        },
        error: function() {
          return AntiModals.overlay('errorModal', {data: {err: 'Could not post flag. Please check your internet connection.'}});
        }
      });

    }

});

Template.myRatingBox.onCreated(function() {

  this.buttonState = new ReactiveVar(0);
  var myReview = this.get('myReview').get();
  if (_.isNumber(myReview.rating))
    this.buttonState.set(myReview.rating > 1 ? 1 : -1);

});

Template.myRatingBox.helpers({
  buttonState: function() { return Template.instance().buttonState.get(); }
});

Template.myRatingBox.events({

  'click [data-rating]': function(evt, tmp) {
    var ratingNumber = parseInt($(evt.currentTarget).data('rating'), 10);

    // If the user is logged in, we can save this into memory.
    if (Meteor.userId()) {
      // In general, we want to store the new rating as well as set writeReview to true.
      var myReview = tmp.get('myReview').get();
      myReview.rating = ratingNumber;
      tmp.get('myReview').set(myReview);
      tmp.get('validateReview').call();
      tmp.get('writeReview').set(true);
    } else {
      // Otherwise, redirect to login.
      var currentPath = FlowRouter.current();
      AppMarket.loginRedirect = FlowRouter.path(currentPath.route.name, currentPath.params, _.extend({}, currentPath.queryParams, {rateApp: true, ratingNumber: ratingNumber}));
      FlowRouter.go('login');
    }
  },

  'click [data-button-state]': function(evt, tmp) {

    tmp.buttonState.set(parseInt($(evt.currentTarget).data('button-state'), 10));

  },

  'click [data-action="bug-modal"]': function(evt, tmp) {

    var app = Template.parentData();
    if (app.onGithub()) {
      AntiModals.overlay('messageModal', {data: {
        header: 'Oh Snap!',
        message: 'Would you like to file a bug report to ' +
                 'let the author know it\'s broken? '
      }});
    }

  }

});
<commit_msg>Hotfix for bug introduced in #59<commit_after>var REVIEW_COLS = 2;

Template.SingleApp.onDestroyed(function() {
  AppMarket.setPageTitlePrefix('');
});

Template.SingleApp.onCreated(function() {
  var tmp = this;
  tmp.appId = FlowRouter.getParam('appId');
  tmp.ready = new ReactiveVar(false);
  tmp.readMore = new ReactiveVar(false);
  tmp.autorun(function () {
    try {
      if (AppMarket.ensureDetailsFetched(tmp.appId)) {
        tmp.ready.set(true);
        var app = Apps.findOne(tmp.appId);
        AppMarket.setPageTitlePrefix(app.name);
        if (!app.screenshots.length) tmp.readMore.set(true);
      }
    } catch (err) {
      if (err.toString().startsWith("Error: failed [404]")) {
        BlazeLayout.render('MasterLayout', {mainSection: 'NotFound'});
        return;
      }
      console.error(err);
      // TODO: This doesn't work as there is no `errorModal` template.
      AntiModals.overlay('errorModal', {data: {err: 'There was an error loading app data from the server'}});
    }
  });

  tmp.flagApp = new ReactiveVar(!!FlowRouter.current().queryParams.flag);
  tmp.writeReview = new ReactiveVar(false);
  tmp.myReview = new ReactiveVar({});
  tmp.reviewExistsServerSide = new ReactiveVar(false);
  tmp.reviewValidator = new SimpleSchema({
    rating: {
      type: Number,
      min: 0,
      max: 3
    },
    text: {
      type: String,
      max: 500,
      optional: true
    }
  }).namedContext();
  tmp.reviewValid = new ReactiveVar(false);
  tmp.validateReview = function() {
    var review = tmp.myReview.get();
    var valid = tmp.reviewValidator.validate(review);
    tmp.reviewValid.set(valid);
    return valid;
  };

  // Load existing review of this app (if it exists)
  tmp.subscribe('reviews', tmp.appId, function() {
    var myReview = Reviews.findOne({
      userId: Meteor.userId(),
      appId: tmp.appId
    });
    if (myReview) {
      tmp.myReview.set(_.pick(myReview, ['text', 'rating']));
      tmp.reviewExistsServerSide.set(true);
    }
  });

});

Template.SingleApp.onRendered(function() {

  var tmp = this;

  if (FlowRouter.getQueryParam('rateApp') && FlowRouter.getQueryParam('ratingNumber')) {

    var myReview = tmp.get('myReview').get();
    myReview.rating = parseInt(FlowRouter.getQueryParam('ratingNumber'), 10);
    tmp.get('myReview').set(myReview);
    tmp.get('validateReview').call();
    tmp.get('writeReview').set(true);
    // I cannot get this to work without a timeout, despite apparently waiting
    // for everyhting else to be ready
    $(document).ready(function() {
      Tracker.afterFlush(function() {
        Meteor.setTimeout(function() {
          var reviewsSection = $('.reviews-section');
          reviewsSection.length && $(window).scrollTo(reviewsSection[0]);
          $('[data-field="review-text"]').focus();
        }, 50);
      });
    });
  }

});

Template.SingleApp.helpers({

  app: function() {

    return Apps.findOne(Template.instance().appId);

  },

  installed: function() {

    return false;
    // return (Meteor.user() && this._id in Meteor.user().installedApps);

  },

  hasSandstormHost: function () {
    return AppMarket.hasSandstormHost();
  },

  getDescription: function() {

    return Template.instance().readMore.get() ? this.description : s.prune(this.description, 1200);

  },

  extendedDescription: function() {

    return this.description && this.description.length > 1200;

  },

  flagApp: function() {

    return Template.instance().flagApp.get();

  },

  flagged: function() {

    Template.instance().flagApp.dep.depend();
    var flags = amplify.store('sandstormAppFlags');
    return flags && this._id in flags;

  },

  flagDetails: function() {

    var flags = amplify.store('sandstormAppFlags');
    return flags && flags[this._id];
  },

  readMore: function() {

    return Template.instance().readMore.get();

  },

  writeReview: function() {
    return Template.instance().writeReview.get();

  },

  myReview: function() {

    return Template.instance().myReview.get();

  },

  shouldShowEditReviewLink: function() {
    return (Template.instance().reviewExistsServerSide.get() &&
            !Template.instance().writeReview.get());
  },

  reviewExistsServerSide: function() {
    return Template.instance().reviewExistsServerSide.get();
  },

  reviewValid: function() {

    return Template.instance().reviewValid.get();

  },

  reviews: function() {

    return Reviews.find({appId: Template.instance().appId}).fetch();

  },

  renderedChangelog: function() {

    return this.changelog && htmlTruncate(marked(this.changelog), 200);

  },

  amazingCount: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return reviewData.ratings.amazing;
  },

  amazingPercent: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return (100 * reviewData.ratings.amazing / reviewData.ratingsCount);
  },

  goodCount: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return reviewData.ratings.jobDone;
  },

  goodPercent: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return (100 * reviewData.ratings.jobDone / reviewData.ratingsCount);
  },

  notSoGoodCount: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return reviewData.ratings.didntLike;
  },

  notSoGoodPercent: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return (100 * reviewData.ratings.didntLike / reviewData.ratingsCount);
  },

  needsFixCount: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return reviewData.ratings.broken;
  },

  needsFixPercent: function() {
    var reviewData = AggregateReviews.findOne({appId: this.appId}) || {ratings: {}};
    return (100 * reviewData.ratings.broken / reviewData.ratingsCount);
  },

});

Template.SingleApp.events({

  'click [data-action="read-more"]': function(evt, tmp) {

    tmp.readMore.set(!tmp.readMore.get());

  },

  'click [data-action="read-more"]': function(evt, tmp) {

    tmp.readMore.set(!tmp.readMore.get());

  },

  'input [data-field="review-text"]': function(evt, tmp) {

    var review = tmp.myReview.get();
    review.text = $(evt.currentTarget).val();
    tmp.myReview.set(review);
    tmp.validateReview();

  },

  'click [data-action="discard-review"]': function(evt, tmp) {

    tmp.myReview.set({});
    Meteor.call('user/discardReview', tmp.appId, AppMarket.redirectOrErrorCallback(null, function() {
      tmp.writeReview.set(false);
    }));

  },

  'click [data-action="cancel-review"]': function(evt, tmp) {
    // Delete no local data, but set `writeReview` to false.
    tmp.writeReview.set(false);
  },

  'click [data-action="submit-review"]': function(evt, tmp) {

    if (tmp.reviewValid.get()) {
      Meteor.call('user/reviewApp', tmp.appId, tmp.myReview.get(), AppMarket.redirectOrErrorCallback(null, function() {
        tmp.writeReview.set(false);
      }));
    }

  },

  'mouseover [data-action="review-tooltip"]': function(evt, tmp) {

    if (!tmp.reviewValid.get()) {
      Tooltips.setClasses(['invalid']);
      Tooltips.show(tmp.$('.my-rating-box')[0], 'You need to choose a rating', 's');
      Tooltips.hideDelay(3000, 500);
    }

  },

  'click [data-action="edit-review"]': function(evt, tmp) {
    tmp.writeReview.set(true);
    tmp.get('validateReview').call();
  },

  'click [data-action="flag-app"]': function(evt, tmp) {

    if (Meteor.userId()) tmp.get('flagApp').set(!tmp.get('flagApp').get());
    else {
      AppMarket.loginRedirect = FlowRouter.current().path;
      FlowRouter.go('login');
    }

  },

});

Template.flagBox.onRendered(function() {

  if (this.data && this.data.cat) {
    if (!this.$('input[type="radio"][data-category="' + this.data.cat + '"]').length) {
      this.$('input[type="radio"][data-category="other"]').prop("checked", true);
      this.$('[data-field="flag-other"]').val(this.data.cat);
    }
    else this.$('input[type="radio"][data-category="' + this.data.cat + '"]').prop("checked", true);
  }

});

Template.flagBox.events({

  'click [data-action="flag-app"]': function(evt, tmp) {

    if (Meteor.userId()) tmp.get('flagApp').set(!tmp.get('flagApp').get());

  }

});

Template.carousel.onRendered(function() {

  var tmp = this;

  tmp.$(".owl-carousel").owlCarousel({
    items: Meteor.Device.isPhone() ? 1 : 2,
    loop: true,
    mouseDrag: false,
    nav: false,
    navRewind: false,
    navText: [''next'',''prev''],
    dots: false,
    onInitialized: function() {
      tmp.$('[data-action="image-lightbox"]').each(function() {
        var $el = $(this);
        Meteor.defer(function() {
          $el.magnificPopup({
            items: {
              src: $el.data('src')
            },
            type: 'image',
            disableOn: 400,
            zoom: {
              enabled: true,
              duration: 300,
              easing: 'ease-in-out',
              opener: function() {
                return $el;
              }
            }
          });
        });
      });
    }
  });

});

Template.carousel.helpers({

  extraScreenshots: function() {
    return this.images && this.images.length - (Meteor.Device.isPhone() ? 1 : 2) > 0;
  }

});

Template.carousel.events({

  'click [data-action="carousel-prev"]:not(.disabled)': function (evt, tmp) {

    tmp.$('.owl-carousel').trigger('prev.owl.carousel');

  },

  'click [data-action="carousel-next"]:not(.disabled)': function (evt, tmp) {

    tmp.$('.owl-carousel').trigger('next.owl.carousel');

  },

  'click [data-action="image-lightbox"]': function(evt, tmp) {

    tmp.$(evt.currentTarget).magnificPopup({
        items: {
          src: tmp.$(evt.currentTarget).data('src')
        },
        type: 'image',
        disableOn: 400,
        zoom: {
          enabled: true,
          duration: 300,
          easing: 'ease-in-out',
          opener: function() {
            return tmp.$(evt.currentTarget);
          }
        }
    });

  }

});

Template.reviewFrame.onRendered(function() {

  var tmp = this;

  tmp.autorun(function() {
    if (Template.currentData()) Tracker.afterFlush(function() {
      tmp.$(".owl-carousel").owlCarousel({
        items: REVIEW_COLS,
        loop: true,
        mouseDrag: false,
        nav: false,
        navRewind: false,
        dots: false
      });
    });

  });

});

Template.reviewFrame.helpers({
  reviewGroups: function() {

    if (!this.reviews) return [];

    var size = Math.ceil(this.reviews.length / REVIEW_COLS);

    var res = _.groupBy(this.reviews, function(reviews, i) {
        return Math.floor(i / size);
    });
    return _.values(res);

  }
});

Template.reviewFrame.events({

  'click [data-action="reviews-prev"]': function (evt, tmp) {

    tmp.$('.owl-carousel').trigger('prev.owl.carousel');

  },

  'click [data-action="reviews-next"]': function (evt, tmp) {

    tmp.$('.owl-carousel').trigger('next.owl.carousel');

  }

});

Template.flagBox.events({

    'click [data-action="flag-app"]': function(evt, tmp) {

      if (Meteor.userId()) tmp.get('flagApp').set(!tmp.get('flagApp').get());
      else {
        AppMarket.loginRedirect = FlowRouter.current().path;
        FlowRouter.go('login');
      }

    },

    'click [data-action="submit-flag-app"]': function(evt, tmp) {

      var cat = tmp.$('input[name="flag-reason"]:checked').data('category'),
          additional = tmp.$('[data-field="additional-info"]').text(),
          flag;

      if (!cat) return;
      else if (cat === 'other') cat = tmp.$('[data-field="flag-other"]').val() || cat;

      flag = {
        cat: cat,
        additional: additional
      };
      var localFlags = amplify.store('sandstormAppFlags') || {};
      localFlags[tmp.get('appId')] = flag;

      $.ajax({
        method: 'POST',
        url: Meteor.settings && Meteor.settings.public && Meteor.settings.public.FLAG,
        data: flag,
        success: function() {
          amplify.store('sandstormAppFlags', localFlags);
          return tmp.get('flagApp').set(false);
        },
        error: function() {
          return AntiModals.overlay('errorModal', {data: {err: 'Could not post flag. Please check your internet connection.'}});
        }
      });

    }

});

Template.myRatingBox.onCreated(function() {

  this.buttonState = new ReactiveVar(0);
  var myReview = this.get('myReview').get();
  if (_.isNumber(myReview.rating))
    this.buttonState.set(myReview.rating > 1 ? 1 : -1);

});

Template.myRatingBox.helpers({
  buttonState: function() { return Template.instance().buttonState.get(); }
});

Template.myRatingBox.events({

  'click [data-rating]': function(evt, tmp) {
    var ratingNumber = parseInt($(evt.currentTarget).data('rating'), 10);

    // If the user is logged in, we can save this into memory.
    if (Meteor.userId()) {
      // In general, we want to store the new rating as well as set writeReview to true.
      var myReview = tmp.get('myReview').get();
      myReview.rating = ratingNumber;
      tmp.get('myReview').set(myReview);
      tmp.get('validateReview').call();
      tmp.get('writeReview').set(true);
    } else {
      // Otherwise, redirect to login.
      var currentPath = FlowRouter.current();
      AppMarket.loginRedirect = FlowRouter.path(currentPath.route.name, currentPath.params, _.extend({}, currentPath.queryParams, {rateApp: true, ratingNumber: ratingNumber}));
      FlowRouter.go('login');
    }
  },

  'click [data-button-state]': function(evt, tmp) {

    tmp.buttonState.set(parseInt($(evt.currentTarget).data('button-state'), 10));

  },

  'click [data-action="bug-modal"]': function(evt, tmp) {

    var app = Template.parentData();
    if (app.onGithub()) {
      AntiModals.overlay('messageModal', {data: {
        header: 'Oh Snap!',
        message: 'Would you like to file a bug report to ' +
                 'let the author know it\'s broken? '
      }});
    }

  }

});
<|endoftext|>


<commit_before>(function() {

    'use strict';

    /**
     * @ngdoc controller
     * @name requisition-search.RequisitionViewController
     * @description
     * Controller for requisition view page
     */

    angular
        .module('requisition-search')
        .controller('RequisitionSearchController', RequisitionSearchController);

    RequisitionSearchController.$inject = [
        '$rootScope', '$state', 'facilityList', 'requisitionService', 'REQUISITION_STATUS',
        'dateUtils', 'loadingModalService', 'notificationService', 'offlineService', 'localStorageFactory'
    ];

    function RequisitionSearchController($rootScope, $state, facilityList, requisitionService,
                                         REQUISITION_STATUS, dateUtils, loadingModalService,
                                         notificationService, offlineService, localStorageFactory) {

        var vm = this,
            offlineRequisitions = localStorageFactory('requisitions');

        vm.loadPrograms = loadPrograms;
        vm.search = search;
        vm.openRnr = openRnr;
        vm.removeOfflineRequisition = removeOfflineRequisition;

        vm.isOfflineDisabled = isOfflineDisabled;
        vm.searchOffline = offlineService.isOffline();
        vm.facilities = facilityList;
        vm.statuses = REQUISITION_STATUS.$toList();
        vm.selectedStatuses = [];

        if (!angular.isArray(vm.facilities) || vm.facilities.length < 1) {
            vm.error = 'msg.facilities.not.found';
        }

        function isOfflineDisabled() {
            if (offlineService.isOffline()) vm.searchOffline = true;
            return offlineService.isOffline();
        }

        /**
         *
         * @ngdoc function
         * @name openRnr
         * @methodOf requisition-search.RequisitionViewController
         *
         * @description
         * Redirect to requisition page after clicking on grid row.
         *
         */
        function openRnr(requisitionId) {
            $state.go('requisitions.requisition.fullSupply', {
                rnr: requisitionId
            });
        }

        /**
         *
         * @ngdoc function
         * @name openRnr
         * @methodOf requisition-search.RequisitionViewController
         *
         * @description
         * Redirect to requisition page after clicking on grid row.
         *
         */
        function removeOfflineRequisition(requisition) {
            offlineRequisitions.removeBy('id', requisition.id);
            requisition.$availableOffline = false;
        }

        /**
         *
         * @ngdoc function
         * @name loadPrograms
         * @methodOf requisition-search.RequisitionViewController
         *
         * @description
         * Loads selected facility supported programs to program select input.
         *
         */
        function loadPrograms() {
            if (!vm.selectedFacility) {
                return;
            } else if (vm.selectedFacility.supportedPrograms) {
                vm.programs = vm.selectedFacility.supportedPrograms;
            } else {
                notificationService.error('msg.no.program.available');
            }
        }

        /**
         *
         * @ngdoc function
         * @name search
         * @methodOf requisition-search.RequisitionViewController
         *
         * @description
         * Searches requisitions by criteria selected in form.
         *
         */
        function search() {
            if (vm.selectedFacility) {
                vm.error = null;
                loadingModalService.open();
                requisitionService.search(vm.searchOffline, {
                        program: vm.selectedProgram ? vm.selectedProgram.id : null,
                        facility: vm.selectedFacility ? vm.selectedFacility.id : null,
                        createdDateFrom: vm.startDate ? vm.startDate.toISOString() : null,
                        createdDateTo: vm.endDate ? vm.endDate.toISOString() : null
                    })
                    .then(function(requisitionList) {
                        vm.requisitionList = requisitionList;
                        loadingModalService.close();
                    })
                    .catch(function() {
                        notificationService.error('msg.error.occurred');
                    })
                    .finally(loadingModalService.close);
            } else {
                notificationService.error('msg.no.facility.selected');
            }
        }
    }
})();
<commit_msg>OLMIS-1643: fixed some docs to match convention<commit_after>(function() {

    'use strict';

    /**
     * @ngdoc controller
     * @name requisition-search.RequisitionViewController
     *
     * @description
     * Controller for requisition view page
     */

    angular
        .module('requisition-search')
        .controller('RequisitionSearchController', RequisitionSearchController);

    RequisitionSearchController.$inject = [
        '$rootScope', '$state', 'facilityList', 'requisitionService', 'REQUISITION_STATUS',
        'dateUtils', 'loadingModalService', 'notificationService', 'offlineService', 'localStorageFactory'
    ];

    function RequisitionSearchController($rootScope, $state, facilityList, requisitionService,
                                         REQUISITION_STATUS, dateUtils, loadingModalService,
                                         notificationService, offlineService, localStorageFactory) {

        var vm = this,
            offlineRequisitions = localStorageFactory('requisitions');

        vm.loadPrograms = loadPrograms;
        vm.search = search;
        vm.openRnr = openRnr;
        vm.removeOfflineRequisition = removeOfflineRequisition;

        vm.isOfflineDisabled = isOfflineDisabled;
        vm.searchOffline = offlineService.isOffline();
        vm.facilities = facilityList;
        vm.statuses = REQUISITION_STATUS.$toList();
        vm.selectedStatuses = [];

        if (!angular.isArray(vm.facilities) || vm.facilities.length < 1) {
            vm.error = 'msg.facilities.not.found';
        }

        function isOfflineDisabled() {
            if (offlineService.isOffline()) vm.searchOffline = true;
            return offlineService.isOffline();
        }

        /**
         * @ngdoc method
         * @name openRnr
         * @methodOf requisition-search.RequisitionViewController
         *
         * @description
         * Redirect to requisition page after clicking on grid row.
         *
         * @param {String} requisitionId Requisition UUID
         */
        function openRnr(requisitionId) {
            $state.go('requisitions.requisition.fullSupply', {
                rnr: requisitionId
            });
        }

        /**
         * @ngdoc method
         * @name removeOfflineRequisition
         * @methodOf requisition-search.RequisitionViewController
         *
         * @description
         * Removes requisition from local storage.
         *
         * @param {Resource} requisition Requisition to remove
         */
        function removeOfflineRequisition(requisition) {
            offlineRequisitions.removeBy('id', requisition.id);
            requisition.$availableOffline = false;
        }

        /**
         * @ngdoc method
         * @name loadPrograms
         * @methodOf requisition-search.RequisitionViewController
         *
         * @description
         * Loads selected facility supported programs to program select input.
         */
        function loadPrograms() {
            if (!vm.selectedFacility) {
                return;
            } else if (vm.selectedFacility.supportedPrograms) {
                vm.programs = vm.selectedFacility.supportedPrograms;
            } else {
                notificationService.error('msg.no.program.available');
            }
        }

        /**
         * @ngdoc method
         * @name search
         * @methodOf requisition-search.RequisitionViewController
         *
         * @description
         * Searches requisitions by criteria selected in form.
         */
        function search() {
            if (vm.selectedFacility) {
                vm.error = null;
                loadingModalService.open();
                requisitionService.search(vm.searchOffline, {
                        program: vm.selectedProgram ? vm.selectedProgram.id : null,
                        facility: vm.selectedFacility ? vm.selectedFacility.id : null,
                        createdDateFrom: vm.startDate ? vm.startDate.toISOString() : null,
                        createdDateTo: vm.endDate ? vm.endDate.toISOString() : null
                    })
                    .then(function(requisitionList) {
                        vm.requisitionList = requisitionList;
                        loadingModalService.close();
                    })
                    .catch(function() {
                        notificationService.error('msg.error.occurred');
                    })
                    .finally(loadingModalService.close);
            } else {
                notificationService.error('msg.no.facility.selected');
            }
        }
    }
})();
<|endoftext|>


<commit_before>'use strict';

angular.module('issueTrackingSystem.controllers.dashboard', [
    'issueTrackingSystem.services.project',
    'issueTrackingSystem.services.issue',
    'issueTrackingSystem.filters.project'
])
    .config(['$routeProvider', function($routeProvider){
        $routeProvider.when('/dashboard/:id', {
            templateUrl: 'app/templates/dashboard.html',
            controller: 'DashboardController'
        })
    }])
    .controller('DashboardController', [
        '$scope',
        '$routeParams',
        'ProjectServices',
        'IssueServices',
        function DashboardController($scope, $routeParams, ProjectServices, IssueServices) {
            var projectIdArray = [];
            var projects = [];

            IssueServices.GetMyIssues($routeParams.id)
                .then(function(success){
                    $scope.Issues = success;
                   success.Issues.forEach(function(element) {
                       if (projectIdArray.indexOf(element.Project.Id) < 0) {
                            projectIdArray.push(element.Project.Id);
                            ProjectServices.GetProjectById(element.Project.Id)
                                .then(function (project) {
                                    projects.push(project);
                                });
                        }
                   }, this);
                   $scope.Projects = projects;
                });

            $scope.range = function(min, max, step) {
                step = step || 1;
                var input = [];
                for (var i = min; i <= max; i += step) {
                    input.push(i);
                }
                    return input;
                };
        }
    ]);<commit_msg>Fix affiliated projects<commit_after>'use strict';

angular.module('issueTrackingSystem.controllers.dashboard', [
    'issueTrackingSystem.services.project',
    'issueTrackingSystem.services.issue',
    'issueTrackingSystem.filters.project'
])
    .config(['$routeProvider', function($routeProvider){
        $routeProvider.when('/dashboard/:id', {
            templateUrl: 'app/templates/dashboard.html',
            controller: 'DashboardController'
        })
    }])
    .controller('DashboardController', [
        '$scope',
        '$routeParams',
        'ProjectServices',
        'IssueServices',
        function DashboardController($scope, $routeParams, ProjectServices, IssueServices) {
            var projectIdArray = [];
            var projects = [];
            $scope.Projects = [];
            ProjectServices.GetAllProjects()
                .then(function (success) {
                    var projects = [];
                    projects = success;
                    projects = projects.filter(isCurrentUsersProject)
                    $scope.Projects = projects;
                });

            function isCurrentUsersProject(element) {
                var result = element.Lead.Username == sessionStorage['CurrentUser'];
                //console.log(result, element);
                return result;
            }

            IssueServices.GetMyIssues($routeParams.id)
                .then(function(success){
                    $scope.Issues = success;
                   success.Issues.forEach(function(element) {
                       if (projectIdArray.indexOf(element.Project.Id) < 0) {
                            projectIdArray.push(element.Project.Id);
                            ProjectServices.GetProjectById(element.Project.Id)
                                .then(function (project) {
                                    $scope.Projects.push(project);
                                });
                        }
                   }, this);
                });

            $scope.range = function(min, max, step) {
                step = step || 1;
                var input = [];
                for (var i = min; i <= max; i += step) {
                    input.push(i);
                }
                    return input;
                };
        }
    ]);<|endoftext|>


<commit_before>/*
    Pseudo enum listing all the events used within the p4ng app for the communications between the clients and the server
*/

module.exports = {
    /*
        When a new player is connected, after having chosen a name, the client sends {name: "Jacques"}
        The server registers the new player, gives him and ID and puts him in the main lobby
        The server sends back {name: "Jacques", id: 47}
        This event is also used for multiple players on the same client
    */
    NewPlayer : "NewPlayer",

    /*
        Whenever needed (player has just connected, returns to the main lobby after a game is finished...) the server will send a client
        a list of all the available rooms, i.e., the rooms where the maximum number of players isn't reached and for which the game hasn't started yet
        The JSON will look like an array of this
        {
            roomId: 47,
            roomName: 'Chill game cyka blyat',
            players: array of player JSON
        }

        TO DISCUSS : to not overload the traffic, we perhaps shouldn't send the info that a new game is created to all players in the main lobby
        to keep their view up-to-date, but just send the list at key moments as mentioned above and add a refresh button in the client.
        The room may contain a lot of other attributes, like a difficulty (casu, hardcore), a password, a max number of player etc that will be clarified
        later in the development.

    */
    GetAvailableRooms : "GetAvailableRooms",

    /*
        When a player wants to create a room, he specifies a name. Other attributes may be added in the future
        The client sends {id: 47, roomName: 'pong fever'}
        If the player isn't in a room yet, he will be moved from the main lobby to his newly created room
        The server sends {roomdId: 47, roomName: 'pong fever'}
        If not, the server sends back the same JSON (may change if not clear enough)

        NOTE : the roomId is always equal to the id of its creator

    */
    CreateRoom : "CreateRoom",

    /*
        When a player wants to join a room, the client sends {id: 47, roomId: 69}
        If the room is accessible and everything's okay, the server removes the player from the main lobby, puts him in that room and returns the same JSON
        Futhermore, every player in the room (including the new one) is sent a list of all the players in that room
        If not, the server returns {id: 47, roomId: -1}
    */
    JoinRoom : "JoinRoom",

    /*
        The client sends {id: 47, roomId: 47}
        Now there are a few cases to consider
        If the player is the creator and the game hasn't started yet, every player in  the room is moved to the main lobby and is sent the same JSON
        Else if the player isn't the creator and the game hasn't started yet, only that player will be moved and sent the same JSON
        If the game has already started, wheter creator or not, only that player is moved and sent the same JSON. He is also considered having lost
        For every case, all the players moved to the main lobby will be sent a list of the available rooms and all the players remaining in the room will
        be sent the list of all the players in the room
        If there is only one remaining player and the game has started, he is considered the winner
        If the player isn't in that room, the server sends back {id: 47, roomId: -1}

        EXTRA : In the case where the creator of the game chooses a random angle at the beginning of each round, if he leaves a running game,
        a new player will have to be chosen as the new "master" of the game
    */
    LeaveRoom : "LeaveRoom",

    /*
        As mentioned above, "In the case where the creator of the game chooses a random angle at the beginning of each round, if he leaves a running game,
        a new player will have to be chosen as the new "master" of the game "
        The server sends {id: 69, roomdId: 47} to the new master that he has elected
    */
    NewMaster : "NewMaster",

    /*
        Whenever needed (player joins/leaves a room), all the players in that room are sent a list of all the players in that room to keep up-to-date
        The server sends a simple array of player JSON
    */
    ListEnrolledPlayers : "ListEnrolledPlayers",

    /*
        When there is at least two players and the creator feels like it, he can send {id: 47, roomId: 47, angle: 0.8}
        where angle is an angle in radians at which the ball will be launched from the middle of the field
        If it's indeed the creator and the game hasn't started yet, every player in the room (including the creator) is sent {angle: 0.8}
        and it signals the effective start of the game
        If not, the client who requested the start is sent {id: 47, roomId: -1, angle: 0.8}
    */
    StartGame : "StartGame",

    /*
        Every x ms, each client sends and array of {id : 47, roomId: 47, coord: 21} i.e., the position of its local player(s)
        Witch each PlayerState, the server will be able to hold the general GameState

        NOTE : although the roomId isn't absolutely mandatory, it can be used to hasten the research in the server, depending on its implementation
    */
    PlayerState : "PlayerState",

    /*
        Every x ms, each client will be sent an array of {id : 47, coord: 21} containing the latest PlayerState present on the server
        of all the players in the game so that it can render the game

        NOTE : this methodology is subject to change, for performance purposes
    */
    GameState : "GameState",

    /*
        When a game ends, every client involved sends
        {
            roomId : 47,
            scores : array of {id: 47, score: 5}
        }
        The server sends back an array of {id: 47, score: 5} after he chose the most commonly attributed score for each player to counter cheating

        NOTE : the state of the game is thus "finished", we can then let the room alive while not everyone has left it (so the winner can brag
        in a potential chat "EZ u all suk lel")
    */
    EndGame : "EndGame"

};<commit_msg>rectifications JoinRoom, LeaveRoom, roomId<commit_after>/*
    Pseudo enum listing all the events used within the p4ng app for the communications between the clients and the server
*/

module.exports = {
    /*
        When a new player is connected, after having chosen a name, the client sends {name: "Jacques"}
        The server registers the new player, gives him and ID and puts him in the main lobby
        The server sends back {name: "Jacques", id: 47}
        This event is also used for multiple players on the same client
    */
    NewPlayer : "NewPlayer",

    /*
        Whenever needed (player has just connected, returns to the main lobby after a game is finished...) the server will send a client
        a list of all the available rooms, i.e., the rooms where the maximum number of players isn't reached and for which the game hasn't started yet
        The JSON will look like an array of this
        {
            roomId: 47,
            roomName: 'Chill game cyka blyat',
            players: array of player JSON
        }

        TO DISCUSS : to not overload the traffic, we perhaps shouldn't send the info that a new game is created to all players in the main lobby
        to keep their view up-to-date, but just send the list at key moments as mentioned above and add a refresh button in the client.
        The room may contain a lot of other attributes, like a difficulty (casu, hardcore), a password, a max number of player etc that will be clarified
        later in the development.

    */
    GetAvailableRooms : "GetAvailableRooms",

    /*
        When a player wants to create a room, he specifies a name. Other attributes may be added in the future
        The client sends {id: 47, roomName: 'pong fever'}
        If the player isn't in a room yet, he will be moved from the main lobby to his newly created room
        The server sends {roomdId: 47, roomName: 'pong fever'}
        If not, the server sends back the same JSON (may change if not clear enough)

    */
    CreateRoom : "CreateRoom",

    /*
        When a player wants to join a room, the client sends {id: 47, roomId: 69}
        If the room is accessible and everything's okay, the server removes the player from the main lobby, puts him in that room and returns the same JSON
        + Every player in the room (except the new one) is sent the new player's JSON to update their view
        + The new player is sent a list of all the players in that room to update his view (ListEnrolledPlayers)
        If not, the server returns {id: 47, roomId: -1}
    */
    JoinRoom : "JoinRoom",

    /*
        The client sends {id: 47, roomId: 47}
        Now there are a few cases to consider
        If the player is the creator and the game hasn't started yet, every player in  the room is moved to the main lobby and is sent the same JSON
        Else if the player isn't the creator and the game hasn't started yet, only that player will be moved and sent the same JSON
        If the game has already started, wheter creator or not, only that player is moved and sent the same JSON. He is also considered having lost
        For every case, all the players moved to the main lobby will be sent a list of the available rooms and all the players remaining in the room will
        be sent the JSON of the player who left the room
        If there is only one remaining player and the game has started, he is considered the winner
        If the player isn't in that room, the server sends back {id: 47, roomId: -1}

        EXTRA : In the case where the creator of the game chooses a random angle at the beginning of each round, if he leaves a running game,
        a new player will have to be chosen as the new "master" of the game
    */
    LeaveRoom : "LeaveRoom",

    /*
        As mentioned above, "In the case where the creator of the game chooses a random angle at the beginning of each round, if he leaves a running game,
        a new player will have to be chosen as the new "master" of the game "
        The server sends {id: 69, roomdId: 47} to the new master that he has elected
    */
    NewMaster : "NewMaster",

    /*
        When a player enters a room he is sent a list of all the players currently in that room
        The server sends a simple array of player JSON
    */
    ListEnrolledPlayers : "ListEnrolledPlayers",

    /*
        When there is at least two players and the creator feels like it, he can send {id: 47, roomId: 47, angle: 0.8}
        where angle is an angle in radians at which the ball will be launched from the middle of the field
        If it's indeed the creator and the game hasn't started yet, every player in the room (including the creator) is sent {angle: 0.8}
        and it signals the effective start of the game
        If not, the client who requested the start is sent {id: 47, roomId: -1, angle: 0.8}
    */
    StartGame : "StartGame",

    /*
        Every x ms, each client sends and array of {id : 47, roomId: 47, coord: 21} i.e., the position of its local player(s)
        Witch each PlayerState, the server will be able to hold the general GameState

        NOTE : although the roomId isn't absolutely mandatory, it can be used to hasten the research in the server, depending on its implementation
    */
    PlayerState : "PlayerState",

    /*
        Every x ms, each client will be sent an array of {id : 47, coord: 21} containing the latest PlayerState present on the server
        of all the players in the game so that it can render the game

        NOTE : this methodology is subject to change, for performance purposes
    */
    GameState : "GameState",

    /*
        When a game ends, every client involved sends
        {
            roomId : 47,
            scores : array of {id: 47, score: 5}
        }
        The server sends back an array of {id: 47, score: 5} after he chose the most commonly attributed score for each player to counter cheating

        NOTE : the state of the game is thus "finished", we can then let the room alive while not everyone has left it (so the winner can brag
        in a potential chat "EZ u all suk lel")
    */
    EndGame : "EndGame"

};<|endoftext|>


<commit_before>b6d4b794-973a-11e6-b514-8086f2004150<commit_msg>Too poor for Dropbox<commit_after>b6d54ace-973a-11e6-b514-8086f2004150<|endoftext|>


<commit_before>import { xhook } from 'xhook';
import Emitter from 'api/emitter';
import Scenarios from 'api/scenarios';
import Requests from 'api/requests';
import MockedRequests from 'api/mocked-requests';

import STATUS_CODES from 'api/constants/status-codes';
import EVENTS from 'api/constants/events';

class XHRInterceptor {

  constructor() {
    xhook.before((request, responder) => {

      // Use this to determine how much time the request took
      request.startTime = Date.now();

      if (!Scenarios.getCurrentMockedRequests()) {
        return responder();
      }

      const mockedRequests = Scenarios.getCurrentMockedRequests();

      for (let mockedRequest of mockedRequests) {
        const methodsMatch = request.method === mockedRequest.method;
        const urlsMatch    = request.url === mockedRequest.url;
        const paramsMatch  = this.checkParamsEquality(request.body, mockedRequest.params);

        if (methodsMatch && urlsMatch && paramsMatch && mockedRequest.active) {
          return this.buildResponse(responder, mockedRequest.getResponse());
        }
      }

      return responder();
    });

    xhook.after((request, response) => {
      Requests.capture(request, response);
      Emitter.emit(EVENTS.REQUEST_CAPTURED);
    });
  }

  enable() {
    xhook.enable();
  }

  disable() {
    xhook.disable();
  }

  buildResponse(responder, responseRule) {
    const response = {
      status: responseRule.status || 200,
      statusText: STATUS_CODES[responseRule.status || 200].toUpperCase(),
      data: responseRule.body,
      headers: JSON.parse(responseRule.headers || '{}')
    };

    return responseRule.delay > 0
      ? setTimeout(() => responder(response), responseRule.delay)
      : responder(response);
  }

  checkParamsEquality(requestBody, responseParams) {
    const requestParams = JSON.parse(requestBody || '{}');
    const parameters    = Object.keys(requestParams);

    for (let key of parameters) {
      const regex = responseParams[key] ? new RegExp(responseParams[key]) : null;

      if (regex && !requestParams[key].match(regex)) {
        return false;
      }
    }

    return true;
  }
}

export default new XHRInterceptor();
<commit_msg>bring back text property on response (in use by jQuery)<commit_after>import { xhook } from 'xhook';
import Emitter from 'api/emitter';
import Scenarios from 'api/scenarios';
import Requests from 'api/requests';
import MockedRequests from 'api/mocked-requests';

import STATUS_CODES from 'api/constants/status-codes';
import EVENTS from 'api/constants/events';

class XHRInterceptor {

  constructor() {
    xhook.before((request, responder) => {

      // Use this to determine how much time the request took
      request.startTime = Date.now();

      if (!Scenarios.getCurrentMockedRequests()) {
        return responder();
      }

      const mockedRequests = Scenarios.getCurrentMockedRequests();

      for (let mockedRequest of mockedRequests) {
        const methodsMatch = request.method === mockedRequest.method;
        const urlsMatch    = request.url === mockedRequest.url;
        const paramsMatch  = this.checkParamsEquality(request.body, mockedRequest.params);

        if (methodsMatch && urlsMatch && paramsMatch && mockedRequest.active) {
          return this.buildResponse(responder, mockedRequest.getResponse());
        }
      }

      return responder();
    });

    xhook.after((request, response) => {
      Requests.capture(request, response);
      Emitter.emit(EVENTS.REQUEST_CAPTURED);
    });
  }

  enable() {
    xhook.enable();
  }

  disable() {
    xhook.disable();
  }

  buildResponse(responder, responseRule) {
    const response = {
      status: responseRule.status || 200,
      statusText: STATUS_CODES[responseRule.status || 200].toUpperCase(),
      data: responseRule.body,
      text: responseRule.body,
      headers: JSON.parse(responseRule.headers || '{}')
    };

    return responseRule.delay > 0
      ? setTimeout(() => responder(response), responseRule.delay)
      : responder(response);
  }

  checkParamsEquality(requestBody, responseParams) {
    const requestParams = JSON.parse(requestBody || '{}');
    const parameters    = Object.keys(requestParams);

    for (let key of parameters) {
      const regex = responseParams[key] ? new RegExp(responseParams[key]) : null;

      if (regex && !requestParams[key].match(regex)) {
        return false;
      }
    }

    return true;
  }
}

export default new XHRInterceptor();
<|endoftext|>


<commit_before>/**
 * Created by johnschroeder on 7/29/15.
 */

var Q = require("q");
var express = require("express");
var router = express.Router();
var dirtyRow = false;

router.route("/:dirtyRow").get(function(req, res) {
    console.log(req.params.dirtyRow);
    dirtyRow = JSON.parse(req.params.dirtyRow);
    var db = require("../../imp_services/impdb.js").connect();

    Q.fcall(db.beginTransaction())
        .then(db.query("USE " + db.databaseName))
        .then(db.query("CALL DeleteCartItem(" + dirtyRow.cartItemID + ")"))
        .then(db.query("CALL ReserveCartItemByPackageSize("
                        + dirtyRow.cartID + ", "
                        + dirtyRow.productID + ", "
                        + "'" + dirtyRow.location + "'" + ", "
                        + dirtyRow.packageCount + ", "
                        + dirtyRow.packageSize + ", "
                        + dirtyRow.sizeMapID + ", "
                        + dirtyRow.packageCount * dirtyRow.packageSize
                        + ");"
        ))
        .then(function(rows) {
            console.log("Put cart item rows[0][0] (should just have remainingQuantityToReserve):");
            console.log(rows[0][0]);
            if(rows[0][0].remainingQuantityToReserve === 0) {
                Q.fcall(db.query("CALL ReserveCartItemBySingles("
                        + dirtyRow.cartID + ", "
                        + dirtyRow.productID + ", "
                        + "'" + dirtyRow.location + "'" + ", "
                        + rows[0][0].remainingQuantityToReserve)
                    + ");")
                    .then(db.commit())
                    .then(db.endTransaction());
            }
            else {
                Q.fcall(db.commit())
                    .then(db.endTransaction());
            }

        })
        .then(function(rows, columns){
            console.log("success sent");
            res.send("Success");
        })
        .catch(function(err){
            Q.fcall(db.rollback())
                .then(db.endTransaction());
            console.log("Error:");
            console.error(err.stack);
            res.status(503).send("ERROR: " + err.code);
        })
        .done();
});
module.exports = router;

/*TODO:
    id the dirty item
    delete cartItem

    amountToReserve = dirty model's requested amount
    location = dirty model's location
    size = dirty model's size
    productID = dirty model's productID

    if(amountToReserve === 0)
        res.send("it worked");

    reserveWrapper(cartID, productID, location, packageCount, packageSize, sizeMapID, toReserve, function() {
        res.send("it worked");
    }

    sp1(cartID, productID, location, packageCount, packageSize, sizeMapID, toReserve):
        iterate through all runs (oldest first) at location for the product
        when it encounters a run with available quantity reserve up to _toReserve using the size,
            and count the number of packages of that size were deducted
        create cartItem
        return _toReserve

    sp2(cartID, productID, location, toReserve):
        get size of singles from productID
        iterate through all runs (oldest first) at location for the product
        when it encounters a run with available quantity reserve up to _toReserve of singles
        create cartItem
        returns _toReserve

    function reserveWrapper(cartID, productID, location, sizeMapID, toReserve, callback)
    {
        calls sp1
        if(toReserve > 0)
            call reserve(cartID, productID, location, toReserve, callback)
        else
            callback()
    }

    function reserve(cartID, productID, location, toReserve, callback)
    {
        hit the sp.
        if(toReserve > 0)
            reserve(cartID, productID, location, toReserve, callback)
        else
            callback()
    }

    delete the cart item

    if(amountToReserve == 0)
    {
        return;
    }

    reserveFromOldest(amountToReserve, callback)
    {
        if(amountToReserve > 0)
        {
            sp(amountToReserve, callback)
            reserveFromOldest(amountToReserve, callback)
        }
    }

    if so:
        delete the cart item
        carry stillToReserve int
        call a recursive function that reserves a quantity from oldest run and returns
    otherwise:
        delete the cart item and replace it with the changed version
        reserve from oldest
 */
<commit_msg>Changing the order of res.send vs endTransaction<commit_after>/**
 * Created by johnschroeder on 7/29/15.
 */

var Q = require("q");
var express = require("express");
var router = express.Router();
var dirtyRow = false;

router.route("/:dirtyRow").get(function(req, res) {
    console.log(req.params.dirtyRow);
    dirtyRow = JSON.parse(req.params.dirtyRow);
    var db = require("../../imp_services/impdb.js").connect();

    Q.fcall(db.beginTransaction())
        .then(db.query("USE " + db.databaseName))
        .then(db.query("CALL DeleteCartItem(" + dirtyRow.cartItemID + ")"))
        .then(db.query("CALL ReserveCartItemByPackageSize("
                        + dirtyRow.cartID + ", "
                        + dirtyRow.productID + ", "
                        + "'" + dirtyRow.location + "'" + ", "
                        + dirtyRow.packageCount + ", "
                        + dirtyRow.packageSize + ", "
                        + dirtyRow.sizeMapID + ", "
                        + dirtyRow.packageCount * dirtyRow.packageSize
                        + ");"
        ))
        .then(function(rows) {
            console.log("Put cart item rows[0][0] (should just have remainingQuantityToReserve):");
            console.log(rows[0][0]);
            if(rows[0][0].remainingQuantityToReserve === 0) {
                Q.fcall(db.query("CALL ReserveCartItemBySingles("
                        + dirtyRow.cartID + ", "
                        + dirtyRow.productID + ", "
                        + "'" + dirtyRow.location + "'" + ", "
                        + rows[0][0].remainingQuantityToReserve)
                    + ");")
                    .then(db.commit())
                    .then(db.endTransaction())
                    .then(res.send("Success"));
            }
            else {
                Q.fcall(db.commit())
                    .then(db.endTransaction())
                    .then(res.send("Success"));
            }

        })
        .catch(function(err){
            Q.fcall(db.rollback())
                .then(db.endTransaction());
            console.log("Error:");
            console.error(err.stack);
            res.status(503).send("ERROR: " + err.code);
        })
        .done();
});
module.exports = router;

/*TODO:
    id the dirty item
    delete cartItem

    amountToReserve = dirty model's requested amount
    location = dirty model's location
    size = dirty model's size
    productID = dirty model's productID

    if(amountToReserve === 0)
        res.send("it worked");

    reserveWrapper(cartID, productID, location, packageCount, packageSize, sizeMapID, toReserve, function() {
        res.send("it worked");
    }

    sp1(cartID, productID, location, packageCount, packageSize, sizeMapID, toReserve):
        iterate through all runs (oldest first) at location for the product
        when it encounters a run with available quantity reserve up to _toReserve using the size,
            and count the number of packages of that size were deducted
        create cartItem
        return _toReserve

    sp2(cartID, productID, location, toReserve):
        get size of singles from productID
        iterate through all runs (oldest first) at location for the product
        when it encounters a run with available quantity reserve up to _toReserve of singles
        create cartItem
        returns _toReserve

    function reserveWrapper(cartID, productID, location, sizeMapID, toReserve, callback)
    {
        calls sp1
        if(toReserve > 0)
            call reserve(cartID, productID, location, toReserve, callback)
        else
            callback()
    }

    function reserve(cartID, productID, location, toReserve, callback)
    {
        hit the sp.
        if(toReserve > 0)
            reserve(cartID, productID, location, toReserve, callback)
        else
            callback()
    }

    delete the cart item

    if(amountToReserve == 0)
    {
        return;
    }

    reserveFromOldest(amountToReserve, callback)
    {
        if(amountToReserve > 0)
        {
            sp(amountToReserve, callback)
            reserveFromOldest(amountToReserve, callback)
        }
    }

    if so:
        delete the cart item
        carry stillToReserve int
        call a recursive function that reserves a quantity from oldest run and returns
    otherwise:
        delete the cart item and replace it with the changed version
        reserve from oldest
 */
<|endoftext|>


<commit_before>$(document).ready(function() {
  var nick = nickgen();
  var chat = $('#chat');

  chat.infoMessage('Connecting ...');

  // open a channel to hydna in read/write mode.
  // NOTE: You must replace <YOUR DOMAIN> with your actual domain to be able
  // to run this script.
  var channel = new HydnaChannel('<YOUR DOMAIN>', 'rw');

  // handle packets as they are received over channel
  channel.onmessage = function(event) {
    // event.data should contain a JSON-encoded object. We'll decode it and
    // look at it's type to see what to do next.
    var packet = JSON.parse(event.data);
    switch(packet.type) {
      case 'join':
        chat.infoMessage(packet.nick + ' has entered the chat!');
        break;
      case 'msg':
        chat.chatMessage(packet.nick, packet.message);
        break;
    }
    // scroll to bottom of chat. this could be disabled when the user has
    // manually scrolled.
    chat.prop('scrollTop', chat.prop('scrollHeight'));
  };

  channel.onerror = function(err) {
    chat.errorMessage('An error has occured. ' + err.message);
  };

  channel.onclose = function(err) {
    chat.infoMessage('Connection closed. Please reload page.');
  }

  channel.onopen = function() {
    // we're connected and the channel is open. display a nice message and
    // broadcast this information to the channel.
    chat.infoMessage('You are now connected and will henceforth be known as "'
                     + nick + '".');
    channel.send(JSON.stringify({
      nick: nick,
      type: 'join'
    }));
  };

  $('#input input').focus();

  $('form').submit(function(event) {
    event.preventDefault();
    var input = $('input', this);
    if (input.val()) {
      channel.send(JSON.stringify({
        nick: nick,
        type: 'msg',
        message: input.val()
      }));

      // clear the input box after sending the message.
      input.val('');
    }
  });
});

// generate a random nickname.
function nickgen() {
  var consonants = 'bcddfghklmmnnprssttv';
  var vocals = 'aaeeiioouuy';
  var length = 4 + Math.floor(Math.random() * 4);
  var nick = [];
  var pool;
  for (var i = 0; i < length; i++) {
    pool = (i % 2?vocals:consonants);
    nick.push(pool.charAt(Math.floor(Math.random() * pool.length)));
  }
  return nick.join('');
}

// return the current time in a nice format
function time() {
  var d = new Date();
  var h = d.getHours();
  var m = d.getMinutes();
  return (h < 12?'0' + h:h) + ':' + (m < 10?'0' + m:m);
}

// append a chat message to an element
$.fn.chatMessage = function(nick, message) {
  // escape nicks and messages to prevent evil users from doing evil things
  nick = nick.replace(/<([^>]+)>/g,'');
  message = message.replace(/<([^>]+)>/g,'');
  $(this).append([
    '
',
    '[',
      time(),
      ']',
      '',
      nick,
      ':',
      message,
      '
'
      ].join(''));
};

// append an info message to an element
$.fn.infoMessage = function(message) {
  message = message.replace(/<([^>]+)>/g,'');
  $(this).append([
    '
',
    '★',
    message,
    '
'
    ].join(''));
};

// append an error message to an element
$.fn.errorMessage = function(message) {
  $(this).append([
    '
',
    '✗',
    message,
    '
'
    ].join(''));
};
<commit_msg>updated domain.<commit_after>$(document).ready(function() {
  var nick = nickgen();
  var chat = $('#chat');

  chat.infoMessage('Connecting ...');

  // open a channel to hydna in read/write mode.
  // NOTE: You must replace <YOUR DOMAIN> with your actual domain to be able
  // to run this script.
  var channel = new HydnaChannel('public.hydna.net/2222', 'rw');

  // handle packets as they are received over channel
  channel.onmessage = function(event) {
    // event.data should contain a JSON-encoded object. We'll decode it and
    // look at it's type to see what to do next.
    var packet = JSON.parse(event.data);
    switch(packet.type) {
      case 'join':
        chat.infoMessage(packet.nick + ' has entered the chat!');
        break;
      case 'msg':
        chat.chatMessage(packet.nick, packet.message);
        break;
    }
    // scroll to bottom of chat. this could be disabled when the user has
    // manually scrolled.
    chat.prop('scrollTop', chat.prop('scrollHeight'));
  };

  channel.onerror = function(err) {
    chat.errorMessage('An error has occured. ' + err.message);
  };

  channel.onclose = function(err) {
    chat.infoMessage('Connection closed. Please reload page.');
  }

  channel.onopen = function() {
    // we're connected and the channel is open. display a nice message and
    // broadcast this information to the channel.
    chat.infoMessage('You are now connected and will henceforth be known as "'
                     + nick + '".');
    channel.send(JSON.stringify({
      nick: nick,
      type: 'join'
    }));
  };

  $('#input input').focus();

  $('form').submit(function(event) {
    event.preventDefault();
    var input = $('input', this);
    if (input.val()) {
      channel.send(JSON.stringify({
        nick: nick,
        type: 'msg',
        message: input.val()
      }));

      // clear the input box after sending the message.
      input.val('');
    }
  });
});

// generate a random nickname.
function nickgen() {
  var consonants = 'bcddfghklmmnnprssttv';
  var vocals = 'aaeeiioouuy';
  var length = 4 + Math.floor(Math.random() * 4);
  var nick = [];
  var pool;
  for (var i = 0; i < length; i++) {
    pool = (i % 2?vocals:consonants);
    nick.push(pool.charAt(Math.floor(Math.random() * pool.length)));
  }
  return nick.join('');
}

// return the current time in a nice format
function time() {
  var d = new Date();
  var h = d.getHours();
  var m = d.getMinutes();
  return (h < 12?'0' + h:h) + ':' + (m < 10?'0' + m:m);
}

// append a chat message to an element
$.fn.chatMessage = function(nick, message) {
  // escape nicks and messages to prevent evil users from doing evil things
  nick = nick.replace(/<([^>]+)>/g,'');
  message = message.replace(/<([^>]+)>/g,'');
  $(this).append([
    '
',
    '[',
      time(),
      ']',
      '',
      nick,
      ':',
      message,
      '
'
      ].join(''));
};

// append an info message to an element
$.fn.infoMessage = function(message) {
  message = message.replace(/<([^>]+)>/g,'');
  $(this).append([
    '
',
    '★',
    message,
    '
'
    ].join(''));
};

// append an error message to an element
$.fn.errorMessage = function(message) {
  $(this).append([
    '
',
    '✗',
    message,
    '
'
    ].join(''));
};
<|endoftext|>


<commit_before>CustomUploadButton = {
    /*
     * Dummy version of Valum's file uploader widget.
     * Overrides everything so I can use a simple file widget.
     * Only necessary so as not to throw errors.
     */
    init: function(o) {
        return this;
    },

    reset: function() {
    }
}

PANDA.views.DataUpload = Backbone.View.extend({
    events: {
        "click #upload-begin":         "begin_event",
        "click #upload-continue":      "continue_event",
        "click #upload-finish":        "finish_event",
        "click #upload-start-over":    "start_over_event",
        "click #step-1-start-over":    "start_over_event",
        "click #step-2-start-over":    "start_over_event",
        "click #step-3-start-over":    "start_over_event"
    },

    file_uploader: null,
    dataset: null,
    upload: null,
    dataset_is_new: false,
    available_space: null,

    allowed_extensions: ["csv", "xls", "xlsx"],

    text: PANDA.text.DataUpload(),

    initialize: function() {
        _.bindAll(this);
    },

    reset: function(dataset_slug) {
        this.file_uploader = null;
        this.upload = null;

        Redd.ajax({
            url: '/check_available_space/',
            dataType: 'json',
            type: 'GET',
            async: false,
            success: _.bind(function(data, status, xhr) {
                this.available_space = $.parseJSON(xhr.responseText);
            }, this),
            error: _.bind(function(xhr, status, error) {
                this.available_space = null;
            }, this)
        });

        if (dataset_slug) {
            this.dataset_is_new = false;
            this.dataset = new PANDA.models.Dataset({ resource_uri: PANDA.API + "/dataset/" + dataset_slug + "/" });

            this.dataset.fetch({
                async: false,
                success: _.bind(function(model, response) {
                    this.render();
                }, this),
                error: _.bind(function(model, response) {
                    if (response.status == 404) {
                        Redd.goto_not_found();
                    } else {
                        Redd.goto_server_error();
                    }
                }, this)
            });
        } else {
            this.dataset_is_new = true;
            this.dataset = null;
            this.render();
        }
    },

    render: function() {
        if (this.dataset) {
            var dataset_json = this.dataset.toJSON();
        } else {
            var dataset_json = null;
        }

        var all_categories = _.reject(Redd.get_categories().toJSON(), function(c) {
            return (c.id == PANDA.settings.UNCATEGORIZED_ID);
        });

        var context = PANDA.utils.make_context({
            text: this.text,
            dataset: dataset_json,
            available_space: this.available_space,
            all_categories:  all_categories
        });

        this.$el.html(PANDA.templates.data_upload(context));

        $('a[rel="popover"]').popover();

        this.file_uploader = new qq.FileUploaderBasic({
            action: "/data_upload/",
            multiple: false,
            allowedExtensions: this.allowed_extensions,
            onSubmit: this.on_submit,
            onProgress: this.on_progress,
            onComplete: this.on_complete,
            showMessage: this.step_one_error_message,
            sizeLimit: PANDA.settings.MAX_UPLOAD_SIZE,
            messages: {
                typeError: "{file} is not a supported type. Only CSV, XLS, and XLSX files are currently supported.",
                sizeError: "{file} is too large, the maximum file size is " + PANDA.settings.MAX_UPLOAD_SIZE + " bytes.",
                emptyError: "{file} is empty.",
                onLeave: "Your file is being uploaded, if you leave now the upload will be cancelled."
            }
        });

        // Create upload button
        var upload_button = CustomUploadButton.init();
        this.file_uploader._button = upload_button;

        $("#upload-file").bind("change", this.on_file_selected);
    },

    on_file_selected: function() {
        var filename = $("#upload-file").val();
        var filepath_parts = $("#upload-file").val().split("\\");
        var filename = filepath_parts[filepath_parts.length - 1];
        var ext = filename.substr(filename.lastIndexOf('.') + 1);
        var no_ext = filename.substr(0, filename.lastIndexOf('.'));

        $("#dataset-name").val(no_ext);

        var known_extensions = ['csv', 'xls', 'xlsx', 'mdb', 'access', 'dbf'];

        $(".csv-options").toggle(ext == 'csv');
        $("#step-1 .notes.xls").toggle(ext == 'xls' || ext == 'xlsx');
        $("#step-1 .notes.access").toggle(ext == 'mdb' || ext == 'access');
        $("#step-1 .notes.dbf").toggle(ext == 'dbf');

        $("#upload-begin").attr("disabled", _.indexOf(this.allowed_extensions, ext) >= 0 ? false : true);
        $("#step-1 .notes.other").toggle(_.indexOf(known_extensions, ext) >= 0 ? false : true);
    },

    on_submit: function(id, fileName) {
        /*
         * Handler for when a file upload starts.
         */
        var params = {
            encoding: $('#upload-encoding').val()
        }

        if (this.dataset) {
            params["dataset_slug"] = this.dataset.get("slug");
        }

        this.file_uploader.setParams(params);

        this.step_two();
    },

    on_progress: function(id, fileName, loaded, total) {
        /*
         * Handler for when a file upload reports its progress.
         *
         * NB: This never files in IE and older FF due to the lack of XHR support.
         */
        var pct = Math.floor(loaded / total * 100);

        $("#step-2 .progress-value").css("width", pct + "%");
        $("#step-2 .progress-value strong").text(pct + "%");
    },

    on_complete: function(id, fileName, responseJSON) {
        /*
         * Handler for when a file upload is completed.
         */
        if (responseJSON.success) {
            this.upload = new PANDA.models.DataUpload(responseJSON);

            // Verify headers match
            if (this.dataset && this.dataset.get("column_schema")) {
                if (!this.upload.get("columns").equals(_.pluck(this.dataset.get("column_schema"), "name"))) {
                    this.step_two_error_message("The columns headers in this file do not match those of the existing data.");
                    return;
                }
            }

            $("#step-2 .ie-progress strong").text("Upload complete!");
            $("#upload-continue").removeAttr("disabled");
        } else if (responseJSON.forbidden) {
            Redd.goto_login(window.location.hash);
        } else if (responseJSON.error_message) {
            this.step_two_error_message(responseJSON.error_message);
        } else if (responseJSON.xhr) {
            this.step_two_error_message(responseJSON.xhr.status + ' ' + responseJSON.xhr.statusText);
        } else {
            this.step_two_error_message('An unexpected error occurred.');
        }
    },

    step_one_error_message: function(message) {
        $("#step-1-alert").alert("alert-error", message + ' <input id="step-1-start-over" type="button" class="btn" value="Try again" />' , false);
    },

    step_two_error_message: function(message) {
        $("#step-2-alert").alert("alert-error", message + ' <input id="step-2-start-over" type="button" class="btn" value="Try again" />' , false);
    },

    step_three_error_message: function(message) {
        $("#step-3-alert").alert("alert-error", message + ' <input id="step-3-start-over" type="button" class="btn" value="Try again" />' , false);
    },

    step_one: function() {
        $(".alert").hide();
        this.on_progress(null, null, 0, 1);

        $("#step-1 .notes.xls").hide();
        $(".csv-options").hide();
    },

    step_two: function() {
        $("#upload-continue").attr("disabled", true);
        $("#dataset-name").focus();
        $("#step-2").collapse({ toggle: true, parent: "#steps" });

        // Use fileuploader's Ajax support detection to determine
        // if we can render a progress bar
        if (!qq.UploadHandlerXhr.isSupported()) {
            $("#step-2 .progress-bar").hide();
            $("#step-2 .ie-progress").show();
        }
    },

    step_three: function() {
        sample_data_html = PANDA.templates.inline_sample_data(this.upload.toJSON());
        $("#step-3 .sample-data").html(sample_data_html);

        $("#upload-finish").removeAttr("disabled");
        $("#upload-start-over").removeAttr("disabled");

        $("#step-3").collapse({ toggle: true, parent: "#steps" });
    },

    begin_event: function() {
        // Initiate upload
        this.file_uploader._onInputChange($("#upload-file")[0]);
    },

    continue_event: function() {
        this.step_three();
    },

    finish_event: function() {
        $("#upload-finish").attr("disabled", true);
        $("#upload-start-over").attr("disabled", true);

        var fileName = this.upload.get("original_filename");

        var categories = $("#dataset-details-form").serializeObject().categories || new Array();

        // If only a single category is selected it will serialize as a string instead of a list
        if (!_.isArray(categories)) {
            categories = [categories];
        }

        categories = _.map(categories, function(cat) {
            return Redd.get_category_by_slug(cat).clone();
        });

        if (!this.dataset) {
            this.dataset = new PANDA.models.Dataset({
                name: $("#dataset-name").val() || fileName.substr(0, fileName.lastIndexOf('.')) || fileName,
                description: $("#dataset-description").val(),
                categories: categories
            });

            this.dataset.save({}, { async: false });
        }

        this.dataset.data_uploads.add(this.upload);
        this.dataset.patch()

        // Begin import, runs synchronously so errors may be caught immediately
        this.dataset.import_data(
            this.upload.get("id"),
            function(dataset) {
                Redd.goto_dataset_view(dataset.get("slug"));
            },
            _.bind(function(dataset, error) {
                // Preemptive import errors (mismatched columns, etc.)
                this.upload.destroy()
                this.step_three_error_message(error.error_message);
            }, this)
        );
    },

    start_over_event: function() {
        $("#upload-finish").attr("disabled", true);
        $("#upload-start-over").attr("disabled", true);

        if (this.upload) {
            this.upload.destroy({ async: false });
            this.upload = null;
        }

        if (this.dataset_is_new && this.dataset) {
            this.dataset.destroy({ async: false });
            this.dataset = null;
        }

        if (this.dataset) {
            this.reset(this.dataset.get("slug"));
        } else {
            this.reset();
        }
    }
});

<commit_msg>Translate internal data upload strings.<commit_after>CustomUploadButton = {
    /*
     * Dummy version of Valum's file uploader widget.
     * Overrides everything so I can use a simple file widget.
     * Only necessary so as not to throw errors.
     */
    init: function(o) {
        return this;
    },

    reset: function() {
    }
}

PANDA.views.DataUpload = Backbone.View.extend({
    events: {
        "click #upload-begin":         "begin_event",
        "click #upload-continue":      "continue_event",
        "click #upload-finish":        "finish_event",
        "click #upload-start-over":    "start_over_event",
        "click #step-1-start-over":    "start_over_event",
        "click #step-2-start-over":    "start_over_event",
        "click #step-3-start-over":    "start_over_event"
    },

    file_uploader: null,
    dataset: null,
    upload: null,
    dataset_is_new: false,
    available_space: null,

    allowed_extensions: ["csv", "xls", "xlsx"],

    text: PANDA.text.DataUpload(),

    initialize: function() {
        _.bindAll(this);
    },

    reset: function(dataset_slug) {
        this.file_uploader = null;
        this.upload = null;

        Redd.ajax({
            url: '/check_available_space/',
            dataType: 'json',
            type: 'GET',
            async: false,
            success: _.bind(function(data, status, xhr) {
                this.available_space = $.parseJSON(xhr.responseText);
            }, this),
            error: _.bind(function(xhr, status, error) {
                this.available_space = null;
            }, this)
        });

        if (dataset_slug) {
            this.dataset_is_new = false;
            this.dataset = new PANDA.models.Dataset({ resource_uri: PANDA.API + "/dataset/" + dataset_slug + "/" });

            this.dataset.fetch({
                async: false,
                success: _.bind(function(model, response) {
                    this.render();
                }, this),
                error: _.bind(function(model, response) {
                    if (response.status == 404) {
                        Redd.goto_not_found();
                    } else {
                        Redd.goto_server_error();
                    }
                }, this)
            });
        } else {
            this.dataset_is_new = true;
            this.dataset = null;
            this.render();
        }
    },

    render: function() {
        if (this.dataset) {
            var dataset_json = this.dataset.toJSON();
        } else {
            var dataset_json = null;
        }

        var all_categories = _.reject(Redd.get_categories().toJSON(), function(c) {
            return (c.id == PANDA.settings.UNCATEGORIZED_ID);
        });

        var context = PANDA.utils.make_context({
            text: this.text,
            dataset: dataset_json,
            available_space: this.available_space,
            all_categories:  all_categories
        });

        this.$el.html(PANDA.templates.data_upload(context));

        $('a[rel="popover"]').popover();

        this.file_uploader = new qq.FileUploaderBasic({
            action: "/data_upload/",
            multiple: false,
            allowedExtensions: this.allowed_extensions,
            onSubmit: this.on_submit,
            onProgress: this.on_progress,
            onComplete: this.on_complete,
            showMessage: this.step_one_error_message,
            sizeLimit: PANDA.settings.MAX_UPLOAD_SIZE,
            messages: {
                typeError: gettext("{file} is not a supported type. Only CSV, XLS, and XLSX files are currently supported."),
                sizeError: interpolate(gettext("{file} is too large, the maximum file size is %(size)s bytes.", { size: PANDA.settings.MAX_UPLOAD_SIZE }, true)),
                emptyError: gettext("{file} is empty."),
                onLeave: gettext("Your file is being uploaded, if you leave now the upload will be cancelled.")
            }
        });

        // Create upload button
        var upload_button = CustomUploadButton.init();
        this.file_uploader._button = upload_button;

        $("#upload-file").bind("change", this.on_file_selected);
    },

    on_file_selected: function() {
        var filename = $("#upload-file").val();
        var filepath_parts = $("#upload-file").val().split("\\");
        var filename = filepath_parts[filepath_parts.length - 1];
        var ext = filename.substr(filename.lastIndexOf('.') + 1);
        var no_ext = filename.substr(0, filename.lastIndexOf('.'));

        $("#dataset-name").val(no_ext);

        var known_extensions = ['csv', 'xls', 'xlsx', 'mdb', 'access', 'dbf'];

        $(".csv-options").toggle(ext == 'csv');
        $("#step-1 .notes.xls").toggle(ext == 'xls' || ext == 'xlsx');
        $("#step-1 .notes.access").toggle(ext == 'mdb' || ext == 'access');
        $("#step-1 .notes.dbf").toggle(ext == 'dbf');

        $("#upload-begin").attr("disabled", _.indexOf(this.allowed_extensions, ext) >= 0 ? false : true);
        $("#step-1 .notes.other").toggle(_.indexOf(known_extensions, ext) >= 0 ? false : true);
    },

    on_submit: function(id, fileName) {
        /*
         * Handler for when a file upload starts.
         */
        var params = {
            encoding: $('#upload-encoding').val()
        }

        if (this.dataset) {
            params["dataset_slug"] = this.dataset.get("slug");
        }

        this.file_uploader.setParams(params);

        this.step_two();
    },

    on_progress: function(id, fileName, loaded, total) {
        /*
         * Handler for when a file upload reports its progress.
         *
         * NB: This never files in IE and older FF due to the lack of XHR support.
         */
        var pct = Math.floor(loaded / total * 100);

        $("#step-2 .progress-value").css("width", pct + "%");
        $("#step-2 .progress-value strong").text(pct + "%");
    },

    on_complete: function(id, fileName, responseJSON) {
        /*
         * Handler for when a file upload is completed.
         */
        if (responseJSON.success) {
            this.upload = new PANDA.models.DataUpload(responseJSON);

            // Verify headers match
            if (this.dataset && this.dataset.get("column_schema")) {
                if (!this.upload.get("columns").equals(_.pluck(this.dataset.get("column_schema"), "name"))) {
                    this.step_two_error_message(gettext("The columns headers in this file do not match those of the existing data."));
                    return;
                }
            }

            $("#step-2 .ie-progress strong").text(gettext("Upload complete!"));
            $("#upload-continue").removeAttr("disabled");
        } else if (responseJSON.forbidden) {
            Redd.goto_login(window.location.hash);
        } else if (responseJSON.error_message) {
            this.step_two_error_message(responseJSON.error_message);
        } else if (responseJSON.xhr) {
            this.step_two_error_message(responseJSON.xhr.status + ' ' + responseJSON.xhr.statusText);
        } else {
            this.step_two_error_message(gettext("An unexpected error occurred."));
        }
    },

    step_one_error_message: function(message) {
        $("#step-1-alert").alert("alert-error", message + ' <input id="step-1-start-over" type="button" class="btn" value="' + gettext("Try again") + '" />' , false);
    },

    step_two_error_message: function(message) {
        $("#step-2-alert").alert("alert-error", message + ' <input id="step-2-start-over" type="button" class="btn" value="' + gettext("Try again") + '" />' , false);
    },

    step_three_error_message: function(message) {
        $("#step-3-alert").alert("alert-error", message + ' <input id="step-3-start-over" type="button" class="btn" value="' + gettext("Try again") + '" />' , false);
    },

    step_one: function() {
        $(".alert").hide();
        this.on_progress(null, null, 0, 1);

        $("#step-1 .notes.xls").hide();
        $(".csv-options").hide();
    },

    step_two: function() {
        $("#upload-continue").attr("disabled", true);
        $("#dataset-name").focus();
        $("#step-2").collapse({ toggle: true, parent: "#steps" });

        // Use fileuploader's Ajax support detection to determine
        // if we can render a progress bar
        if (!qq.UploadHandlerXhr.isSupported()) {
            $("#step-2 .progress-bar").hide();
            $("#step-2 .ie-progress").show();
        }
    },

    step_three: function() {
        sample_data_html = PANDA.templates.inline_sample_data(this.upload.toJSON());
        $("#step-3 .sample-data").html(sample_data_html);

        $("#upload-finish").removeAttr("disabled");
        $("#upload-start-over").removeAttr("disabled");

        $("#step-3").collapse({ toggle: true, parent: "#steps" });
    },

    begin_event: function() {
        // Initiate upload
        this.file_uploader._onInputChange($("#upload-file")[0]);
    },

    continue_event: function() {
        this.step_three();
    },

    finish_event: function() {
        $("#upload-finish").attr("disabled", true);
        $("#upload-start-over").attr("disabled", true);

        var fileName = this.upload.get("original_filename");

        var categories = $("#dataset-details-form").serializeObject().categories || new Array();

        // If only a single category is selected it will serialize as a string instead of a list
        if (!_.isArray(categories)) {
            categories = [categories];
        }

        categories = _.map(categories, function(cat) {
            return Redd.get_category_by_slug(cat).clone();
        });

        if (!this.dataset) {
            this.dataset = new PANDA.models.Dataset({
                name: $("#dataset-name").val() || fileName.substr(0, fileName.lastIndexOf('.')) || fileName,
                description: $("#dataset-description").val(),
                categories: categories
            });

            this.dataset.save({}, { async: false });
        }

        this.dataset.data_uploads.add(this.upload);
        this.dataset.patch()

        // Begin import, runs synchronously so errors may be caught immediately
        this.dataset.import_data(
            this.upload.get("id"),
            function(dataset) {
                Redd.goto_dataset_view(dataset.get("slug"));
            },
            _.bind(function(dataset, error) {
                // Preemptive import errors (mismatched columns, etc.)
                this.upload.destroy()
                this.step_three_error_message(error.error_message);
            }, this)
        );
    },

    start_over_event: function() {
        $("#upload-finish").attr("disabled", true);
        $("#upload-start-over").attr("disabled", true);

        if (this.upload) {
            this.upload.destroy({ async: false });
            this.upload = null;
        }

        if (this.dataset_is_new && this.dataset) {
            this.dataset.destroy({ async: false });
            this.dataset = null;
        }

        if (this.dataset) {
            this.reset(this.dataset.get("slug"));
        } else {
            this.reset();
        }
    }
});

<|endoftext|>


<commit_before>import loggerCreator from "app/utils/logger";
//noinspection JSUnresolvedVariable
const moduleLogger = loggerCreator("LoadingSpinner");

import React, { Component } from "react";
import { Image, StyleSheet, View, ActivityIndicator } from "react-native";

import NormalText from "app/shared_components/text/normal_text";
import { player } from "app/stores/player/player";
import SongDetails from "app/pages/player_page/song_details";

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  // Progress
  progressSpinner: {
    justifyContent: "flex-end",
    paddingBottom: 20,
  },
  progressStatus: {
    justifyContent: "flex-start",
    alignItems: "center",
  },
  progressStatusError: {
    color: "red",
  },
});

export default class LoadingSpinner extends Component {
  render() {
    loggerCreator("render", moduleLogger);

    let loadingError = "";
    if (player.loadingError) {
      loadingError = `Error occurred, retrying: ${player.loadingError}`;
    }

    return (
      <View style={styles.container}>
        <View style={styles.progressSpinner}>
          <ActivityIndicator size="large" />
        </View>
        <View style={styles.progressStatus}>
          {this.props.message
            ? <NormalText>
                {this.props.message}
              </NormalText>
            : null}
          {this.props.song ? <SongDetails song={this.props.song} style={styles.songDetails} /> : null}
          <NormalText style={styles.progressStatusError}>
            {loadingError}
          </NormalText>
        </View>
      </View>
    );
  }
}

LoadingSpinner.propTypes = {
  message: React.PropTypes.string,
  song: React.PropTypes.object,
};
<commit_msg>prettier loading screen<commit_after>import loggerCreator from "app/utils/logger";
//noinspection JSUnresolvedVariable
const moduleLogger = loggerCreator("LoadingSpinner");

import React, { Component } from "react";
import { Image, StyleSheet, View, ActivityIndicator } from "react-native";

import NormalText from "app/shared_components/text/normal_text";
import { player } from "app/stores/player/player";
import SongDetails from "app/pages/player_page/song_details";
import BigText from "app/shared_components/text/big_text";

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  // Progress
  progressSpinner: {
    justifyContent: "flex-end",
    paddingBottom: 20,
  },
  progressStatus: {
    justifyContent: "flex-start",
    alignItems: "center",
  },
  progressStatusError: {
    color: "red",
  },
  songDetails: {
    marginTop: 20,
  },
});

export default class LoadingSpinner extends Component {
  render() {
    loggerCreator("render", moduleLogger);

    let loadingError = "";
    if (player.loadingError) {
      loadingError = `Error occurred, retrying: ${player.loadingError}`;
    }

    return (
      <View style={styles.container}>
        <View style={styles.progressSpinner}>
          <ActivityIndicator size="large" />
        </View>
        <View style={styles.progressStatus}>
          {this.props.message
            ? <BigText>
                {this.props.message}
              </BigText>
            : null}
          {this.props.song ? <SongDetails song={this.props.song} style={styles.songDetails} /> : null}
          <NormalText style={styles.progressStatusError}>
            {loadingError}
          </NormalText>
        </View>
      </View>
    );
  }
}

LoadingSpinner.propTypes = {
  message: React.PropTypes.string,
  song: React.PropTypes.object,
};
<|endoftext|>