<commit_before>import Ember from "ember";
import { translationMacro as t } from "ember-i18n";
import config from '../../config/environment';
const { getOwner } = Ember;

export default Ember.Controller.extend({
  queryParams: ["isUnplannedPackage"],
  isUnplannedPackage: false,
  offerController: Ember.inject.controller('offer'),
  offer: Ember.computed.alias("offerController.model"),
  item: Ember.computed.alias("model"),
  session: Ember.inject.service(),
  store: Ember.inject.service(),
  messageBox: Ember.inject.service(),
  i18n: Ember.inject.service(),
  cordova: Ember.inject.service(),
  offerId: null,
  itemId: null,
  packageId: null,
  noImage: Ember.computed.empty("item.images"),
  previewImage: null,
  addPhotoLabel: t("edit_images.add_photo"),
  isReady: false,
  isExpanded: false,
  backBtnVisible: true,
  loadingPercentage: t("edit_images.image_uploading"),
  uploadedFileDate: null,
  previousRoute: null,

  initActionSheet: function(onSuccess) {
    return window.plugins.actionsheet.show({
      buttonLabels: [this.locale("edit_images.upload").string, this.locale("edit_images.camera").string, this.locale("edit_images.cancel").string]
    }, function(buttonIndex) {
      if (buttonIndex === 1) {
        navigator.camera.getPicture(onSuccess, null, {
          quality: 40,
          destinationType: navigator.camera.DestinationType.DATA_URL,
          sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY
        });
      }
      if (buttonIndex === 2) {
        navigator.camera.getPicture(onSuccess, null, {
          correctOrientation: true,
          quality: 40,
          destinationType: navigator.camera.DestinationType.DATA_URL,
          sourceType: navigator.camera.PictureSourceType.CAMERA
        });
      }
      if (buttonIndex === 3) {
        window.plugins.actionsheet.hide();
      }
    });
  },

  package: Ember.computed('packageId', function() {
    return this.get("store").peekRecord("package", this.get("packageId"));
  }),

  previewMatchesFavourite: Ember.computed("previewImage", "favouriteImage", function() {
    return this.get("previewImage") === this.get("favouriteImage");
  }),

  images: Ember.computed("item.images.[]", function() {
    //The reason for sorting is because by default it's ordered by favourite
    //then id order. If another image is made favourite then deleted the first image
    //by id order is made favourite which can be second image in list which seems random.

    //Sort by id ascending except place new images id = 0 at end
    return (this.get("item.images") || Ember.A()).filterBy('itemId').toArray().sort(function(a, b) {
      a = parseInt(a.get("id"), 10);
      b = parseInt(b.get("id"), 10);
      if (a === 0) { return 1; }
      if (b === 0) { return -1; }
      return a - b;
    });
  }),

  favouriteImage: Ember.computed("item.images.@each.favourite", "package.favouriteImage", function() {
    return this.get("package") ?
      this.get("package.favouriteImage") :
      this.get("images").filterBy("favourite").get("firstObject");
  }),

  initPreviewImage: Ember.on('init', Ember.observer("package", "item", "item.images.[]", function() {
    var path = history.state ? history.state.path : "";
    this.set("previousRoute", path);
    var image = this.get("package.image") || this.get("item.displayImage");
    if (image) {
      this.send("setPreview", image);
    }
  })),

  //css related
  previewImageBgCss: Ember.computed("previewImage", "isExpanded", "previewImage.angle", {

    get() {
      var css = this.get("instructionBoxCss");
      if (!this.get("previewImage")) {
        return css;
      }

      var imgTag = new Image();
      imgTag.onload = () => {
        var newCSS = new Ember.Handlebars.SafeString(
          css + "background-image:url(" + this.get("previewImage.imageUrl") + ");" +
          "background-size: " + (this.get("isExpanded") ? "contain" : "cover") + ";"
        );
        this.set("previewImageBgCss", newCSS);
      };

      imgTag.src = this.get("previewImage.imageUrl");

      return new Ember.Handlebars.SafeString(
        css + "background-image:url('assets/images/image_loading.gif');" +
        "background-size: 'inherit';"
      );
    },

    set(key, value) {
      return value;
    }
  }),

  instructionBoxCss: Ember.computed("previewImage", "isExpanded", function() {
    var height = Ember.$(window).height() * 0.6;
    return new Ember.Handlebars.SafeString("min-height:" + height + "px;");
  }),

  thumbImageCss: Ember.computed(function() {
    var imgWidth = Math.min(120, Ember.$(window).width() / 4 - 14);
    return new Ember.Handlebars.SafeString("width:" + imgWidth + "px; height:" + imgWidth + "px;");
  }),

  noImageLink: Ember.computed("noImage", function() {
    return this.get("noImage") && this.get("session.isAdminApp");
  }),

  locale: function(str) {
    return this.get("i18n").t(str);
  },

  createItem: function(donorCondition, withoutImage, identifier) {
    var _this = this;
    var loadingView = getOwner(this).lookup('component:loading').append();
    var offer = this.get("offer");
    var item = this.get("store").createRecord("item", {
      offer: offer,
      donorCondition: donorCondition,
      state: "draft"
    });
    item.save()
      .then(() => {
        if (withoutImage) {
          loadingView.destroy();
          if (this.get('session.isAdminApp')) {
            _this.transitionToRoute("review_item.accept", _this.get('offer'), item);
          } else {
            _this.send("onNewItem", item);
          }
        } else {
          this.get("store").createRecord('image', { cloudinaryId: identifier, item: item, favourite: true }).save()
            .then(function() {
              _this.send("onNewItem", item);
              loadingView.destroy();
            });
        }
      })
      .catch(error => {
        item.unloadRecord();
        loadingView.destroy();
        throw error;
      });
  },

  deleteOffer: function(loadingView) {
    var controller = this;
    var offer = this.get("offer");
    offer.destroyRecord().then(function() {
        controller.transitionToRoute('my_list.reviewing');
      })
      .finally(() => loadingView.destroy());
  },

  cancelItem: function(controller, item) {
    var offer = item.get('offer');
    var loadingView = getOwner(controller).lookup('component:loading').append();

    if (offer.get('itemCount') === 1) {
      var delivery = offer.get("delivery");
      if (delivery) {
        this.get("messageBox").confirm(this.locale("edit_images.cancelling_item_will_cancel_offer"),
          () => {
            var gogovanOrder = offer.get("delivery.gogovanOrder");
            if (gogovanOrder && gogovanOrder.get("isActive")) {
              loadingView.destroy();
              controller.transitionToRoute("offer.cancel_gogovan", offer);
            } else {
              this.deleteOffer(loadingView);
            }
          });
      } else {
        this.deleteOffer(loadingView);
      }
    } else {
      offer.get('items').removeObject(item);
      item.destroyRecord().then(function() {
          controller.transitionToRoute("review_offer.items");
        })
        .finally(() => loadingView.destroy());
    }
  },

  removeImage: function(controller, item) {

    var img = item.get("images.firstObject");
    var loadingView = getOwner(controller).lookup('component:loading').append();
    img.deleteRecord();
    img.save()
      .then(i => {
        i.unloadRecord();
        if (this.get("session.isAdminApp") && this.get("isUnplannedPackage")) {
          const packageId = this.get("item.packages.firstObject.id");
          controller.transitionToRoute("receive_package", packageId, {
              queryParams: {
                isUnplannedPackage: true
              }
            });
        } else {
          controller.transitionToRoute("item.edit_images", item);
        }
      })
      .finally(() => {
        loadingView.destroy();
        this.set("isExpanded", false);
      });
  },

  confirmRemoveLastImage: function() {
    var item = this.get("item");
    this.get("messageBox").custom(
      this.locale("edit_images.last_image_with_item"),
      this.locale("edit_images.cancel_item"), () => this.cancelItem(this, item),
      this.locale("edit_images.remove_image"), () => this.removeImage(this, item)
    );
  },

  cannotRemoveImageAlert: function() {
    this.get("messageBox").alert(this.locale("edit_images.cant_delete_last_image"));
  },

  actions: {
    next() {
      const offer = this.get("offer");
      const model = this.get("model");
      if (this.get("session.isAdminApp")) {
        if (this.get("isUnplannedPackage")) {
          const packageId = this.get("item.packages.firstObject.id");
          this.transitionToRoute("receive_package", packageId, {
              queryParams: {
                isUnplannedPackage: true
              }
            });
        }else {
          this.transitionToRoute("review_item.accept", offer, model);
        }
      } else {
        this.transitionToRoute("item.edit");
      }
    },

    nextWithoutImage() {
      var item = this.get("item");
      if (item) {
        if (this.get('session.isAdminApp')) {
          if(this.get("isUnplannedPackage")) {
            const packageId = this.get("item.packages.firstObject.id");
            return this.transitionToRoute("receive_package", packageId, {
              queryParams: {
                isUnplannedPackage: true
              }
            });
          }
          return this.transitionToRoute("review_item.accept", this.get('offer'), item);
        }
        else if (!item.get('isOffer')) {
          return this.transitionToRoute("item.edit", item.get("id"));
        }
      }
      const defaultDonorCondition = this.get("store").peekAll("donorCondition").sortBy("id").get("firstObject");
      this.createItem(defaultDonorCondition, true);
    },

    back() {
      if (this.get("session.isAdminApp")) {
        this.transitionToRoute("review_offer.items");
      } else {
        if (this.get('offer.itemCount') === 0) {
          this.transitionToRoute("offers");
        } else {
          this.transitionToRoute("offer.offer_details");
        }
      }
    },

    onNewItem(item) {
      if (this.get("session.isAdminApp")) {
        this.transitionToRoute("item.edit_images", item.get("id"));
      } else {
        this.transitionToRoute("item.edit", item.get("id"));
      }
    },

    setPreview(image) {
      this.get("item.images").setEach("selected", false);
      image.set("selected", true);
      this.set("previewImage", image);
    },

    setFavourite() {
      if (this.get("package")) {
        var pkg = this.get("package");
        pkg.set("favouriteImage", this.get("previewImage"));
        pkg.save()
          .catch(error => { pkg.rollbackAttributes(); throw error; });
      } else {
        this.get("item.images").setEach("favourite", false);
        this.get("previewImage").set("favourite", true);
        this.get("previewImage").save()
          .catch(error => {
            this.get("item.images").forEach(img => img.rollbackAttributes());
            throw error;
          });
      }
    },

    deleteImage() {
      if (this.get("item.images.length") === 1) {
        this.get("session.isAdminApp") ? this.confirmRemoveLastImage() :
          this.cannotRemoveImageAlert();
        return;
      } else {
        this.get("messageBox").confirm(this.get("i18n").t("edit_images.delete_confirm"), () => {
          var loadingView = getOwner(this).lookup('component:loading').append();
          var img = this.get("previewImage");
          img.deleteRecord();
          img.save()
            .then(i => {
              i.unloadRecord();
              this.initPreviewImage();
              if (!this.get("favouriteImage")) {
                this.send("setFavourite");
              }
            })
            .catch(error => { img.rollbackAttributes(); throw error; })
            .finally(() => loadingView.destroy());
        });
      }
    },

    expandImage() {
      this.toggleProperty("isExpanded");
    },

    //file upload
    triggerUpload() {

      // For Cordova application
      if (config.cordova.enabled) {
        var onSuccess = ((function(_this) {
          return function(path) {
            console.log(path);
            var dataURL = "data:image/jpg;base64," + path;

            $("input[type='file']").fileupload('option', 'formData').file = dataURL;
            $("input[type='file']").fileupload('add', { files: [dataURL] });
          };
        })(this));

        this.initActionSheet(onSuccess);
      } else {
        // On the browser
        if (this.get('item.isOffer')) {
          // On the offer page we allow the user to move to the item edition page first
          // and then upload images from there
          return this.send('nextWithoutImage');
        }
        // On the item edition page we trigger the file selection
        if (navigator.userAgent.match(/iemobile/i)) {
          //don't know why but on windows phone need to click twice in quick succession
          //for dialog to appear
          Ember.$("#photo-list input[type='file']").click().click();
        } else {
          Ember.$("#photo-list input[type='file']").trigger("click");
        }
      }
    },

    uploadReady() {
      this.set("isReady", true);
    },

    uploadStart(e, data) {
      this.set("uploadedFileDate", data);
      Ember.$(".loading-image-indicator").show();
    },

    cancelUpload() {
      if (this.get("uploadedFileDate")) { this.get("uploadedFileDate").abort(); }
    },

    uploadProgress(e, data) {
      e.target.disabled = true; // disable image-selection
      var progress = parseInt(data.loaded / data.total * 100, 10) || 0;
      this.set("addPhotoLabel", progress + "%");
      this.set("loadingPercentage", this.get("i18n").t("edit_images.image_uploading") + progress + "%");
    },

    uploadComplete(e) {
      e.target.disabled = false; // enable image-selection
      this.set("uploadedFileDate", null);
      Ember.$(".loading-image-indicator.hide_image_loading").hide();
      this.set("addPhotoLabel", this.get("i18n").t("edit_images.add_photo"));
      this.set("loadingPercentage", this.get("i18n").t("edit_images.image_uploading"));
    },

    uploadSuccess(e, data) {
      var identifier = data.result.version + "/" + data.result.public_id + "." + data.result.format;
      var item = this.get("item");
      if (!item || this.get("item.isOffer")) {
        var defaultDonorCondition = this.get("store").peekAll("donorCondition").sortBy("id").get("firstObject");
        this.createItem(defaultDonorCondition, false, identifier);
      } else {
        var favourite = item.get("images.length") === 0;
        var img = this.get("store").createRecord('image', { cloudinaryId: identifier, item: this.get("item"), favourite: favourite });
        img.save().catch(error => { img.unloadRecord(); throw error; });
      }
    },

    rotateImageRight() {
      var angle = this.get("previewImage.angle");
      angle = (angle + 90) % 360;
      this.send("rotateImage", angle);
    },

    rotateImageLeft() {
      var angle = this.get("previewImage.angle");
      angle = (angle ? (angle - 90) : 270) % 360;
      this.send("rotateImage", angle);
    },

    rotateImage(angle) {
      var image = this.get("previewImage");
      image.set("angle", angle);
      Ember.run.debounce(this, this.saveImageRotation, image, 400);
    }
  },

  saveImageRotation(image) {
    image.save();
  }

});
<commit_msg>GCW-2644 prettier changes<commit_after>import Ember from "ember";
import { translationMacro as t } from "ember-i18n";
import config from '../../config/environment';
const { getOwner } = Ember;

export default Ember.Controller.extend({
  queryParams: ["isUnplannedPackage"],
  isUnplannedPackage: false,
  offerController: Ember.inject.controller('offer'),
  offer: Ember.computed.alias("offerController.model"),
  item: Ember.computed.alias("model"),
  session: Ember.inject.service(),
  store: Ember.inject.service(),
  messageBox: Ember.inject.service(),
  i18n: Ember.inject.service(),
  cordova: Ember.inject.service(),
  offerId: null,
  itemId: null,
  packageId: null,
  noImage: Ember.computed.empty("item.images"),
  previewImage: null,
  addPhotoLabel: t("edit_images.add_photo"),
  isReady: false,
  isExpanded: false,
  backBtnVisible: true,
  loadingPercentage: t("edit_images.image_uploading"),
  uploadedFileDate: null,
  previousRoute: null,

  initActionSheet: function(onSuccess) {
    return window.plugins.actionsheet.show({
      buttonLabels: [this.locale("edit_images.upload").string, this.locale("edit_images.camera").string, this.locale("edit_images.cancel").string]
    }, function(buttonIndex) {
      if (buttonIndex === 1) {
        navigator.camera.getPicture(onSuccess, null, {
          quality: 40,
          destinationType: navigator.camera.DestinationType.DATA_URL,
          sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY
        });
      }
      if (buttonIndex === 2) {
        navigator.camera.getPicture(onSuccess, null, {
          correctOrientation: true,
          quality: 40,
          destinationType: navigator.camera.DestinationType.DATA_URL,
          sourceType: navigator.camera.PictureSourceType.CAMERA
        });
      }
      if (buttonIndex === 3) {
        window.plugins.actionsheet.hide();
      }
    });
  },

  package: Ember.computed('packageId', function() {
    return this.get("store").peekRecord("package", this.get("packageId"));
  }),

  previewMatchesFavourite: Ember.computed("previewImage", "favouriteImage", function() {
    return this.get("previewImage") === this.get("favouriteImage");
  }),

  images: Ember.computed("item.images.[]", function() {
    //The reason for sorting is because by default it's ordered by favourite
    //then id order. If another image is made favourite then deleted the first image
    //by id order is made favourite which can be second image in list which seems random.

    //Sort by id ascending except place new images id = 0 at end
    return (this.get("item.images") || Ember.A()).filterBy('itemId').toArray().sort(function(a, b) {
      a = parseInt(a.get("id"), 10);
      b = parseInt(b.get("id"), 10);
      if (a === 0) { return 1; }
      if (b === 0) { return -1; }
      return a - b;
    });
  }),

  favouriteImage: Ember.computed("item.images.@each.favourite", "package.favouriteImage", function() {
    return this.get("package") ?
      this.get("package.favouriteImage") :
      this.get("images").filterBy("favourite").get("firstObject");
  }),

  initPreviewImage: Ember.on('init', Ember.observer("package", "item", "item.images.[]", function() {
    var path = history.state ? history.state.path : "";
    this.set("previousRoute", path);
    var image = this.get("package.image") || this.get("item.displayImage");
    if (image) {
      this.send("setPreview", image);
    }
  })),

  //css related
  previewImageBgCss: Ember.computed("previewImage", "isExpanded", "previewImage.angle", {

    get() {
      var css = this.get("instructionBoxCss");
      if (!this.get("previewImage")) {
        return css;
      }

      var imgTag = new Image();
      imgTag.onload = () => {
        var newCSS = new Ember.Handlebars.SafeString(
          css + "background-image:url(" + this.get("previewImage.imageUrl") + ");" +
          "background-size: " + (this.get("isExpanded") ? "contain" : "cover") + ";"
        );
        this.set("previewImageBgCss", newCSS);
      };

      imgTag.src = this.get("previewImage.imageUrl");

      return new Ember.Handlebars.SafeString(
        css + "background-image:url('assets/images/image_loading.gif');" +
        "background-size: 'inherit';"
      );
    },

    set(key, value) {
      return value;
    }
  }),

  instructionBoxCss: Ember.computed("previewImage", "isExpanded", function() {
    var height = Ember.$(window).height() * 0.6;
    return new Ember.Handlebars.SafeString("min-height:" + height + "px;");
  }),

  thumbImageCss: Ember.computed(function() {
    var imgWidth = Math.min(120, Ember.$(window).width() / 4 - 14);
    return new Ember.Handlebars.SafeString("width:" + imgWidth + "px; height:" + imgWidth + "px;");
  }),

  noImageLink: Ember.computed("noImage", function() {
    return this.get("noImage") && this.get("session.isAdminApp");
  }),

  locale: function(str) {
    return this.get("i18n").t(str);
  },

  createItem: function(donorCondition, withoutImage, identifier) {
    var _this = this;
    var loadingView = getOwner(this).lookup('component:loading').append();
    var offer = this.get("offer");
    var item = this.get("store").createRecord("item", {
      offer: offer,
      donorCondition: donorCondition,
      state: "draft"
    });
    item.save()
      .then(() => {
        if (withoutImage) {
          loadingView.destroy();
          if (this.get('session.isAdminApp')) {
            _this.transitionToRoute("review_item.accept", _this.get('offer'), item);
          } else {
            _this.send("onNewItem", item);
          }
        } else {
          this.get("store").createRecord('image', { cloudinaryId: identifier, item: item, favourite: true }).save()
            .then(function() {
              _this.send("onNewItem", item);
              loadingView.destroy();
            });
        }
      })
      .catch(error => {
        item.unloadRecord();
        loadingView.destroy();
        throw error;
      });
  },

  deleteOffer: function(loadingView) {
    var controller = this;
    var offer = this.get("offer");
    offer.destroyRecord().then(function() {
        controller.transitionToRoute('my_list.reviewing');
      })
      .finally(() => loadingView.destroy());
  },

  cancelItem: function(controller, item) {
    var offer = item.get('offer');
    var loadingView = getOwner(controller).lookup('component:loading').append();

    if (offer.get('itemCount') === 1) {
      var delivery = offer.get("delivery");
      if (delivery) {
        this.get("messageBox").confirm(this.locale("edit_images.cancelling_item_will_cancel_offer"),
          () => {
            var gogovanOrder = offer.get("delivery.gogovanOrder");
            if (gogovanOrder && gogovanOrder.get("isActive")) {
              loadingView.destroy();
              controller.transitionToRoute("offer.cancel_gogovan", offer);
            } else {
              this.deleteOffer(loadingView);
            }
          });
      } else {
        this.deleteOffer(loadingView);
      }
    } else {
      offer.get('items').removeObject(item);
      item.destroyRecord().then(function() {
          controller.transitionToRoute("review_offer.items");
        })
        .finally(() => loadingView.destroy());
    }
  },

  removeImage: function(controller, item) {

    var img = item.get("images.firstObject");
    var loadingView = getOwner(controller).lookup('component:loading').append();
    img.deleteRecord();
    img.save()
      .then(i => {
        i.unloadRecord();
        if (this.get("session.isAdminApp") && this.get("isUnplannedPackage")) {
          const packageId = this.get("item.packages.firstObject.id");
          controller.transitionToRoute("receive_package", packageId, {
              queryParams: {
                isUnplannedPackage: true
              }
            });
        } else {
          controller.transitionToRoute("item.edit_images", item);
        }
      })
      .finally(() => {
        loadingView.destroy();
        this.set("isExpanded", false);
      });
  },

  confirmRemoveLastImage: function() {
    var item = this.get("item");
    this.get("messageBox").custom(
      this.locale("edit_images.last_image_with_item"),
      this.locale("edit_images.cancel_item"), () => this.cancelItem(this, item),
      this.locale("edit_images.remove_image"), () => this.removeImage(this, item)
    );
  },

  cannotRemoveImageAlert: function() {
    this.get("messageBox").alert(this.locale("edit_images.cant_delete_last_image"));
  },

  actions: {
    next() {
      const offer = this.get("offer");
      const model = this.get("model");
      if (this.get("session.isAdminApp")) {
        if (this.get("isUnplannedPackage")) {
          const packageId = this.get("item.packages.firstObject.id");
          this.transitionToRoute("receive_package", packageId, {
              queryParams: {
                isUnplannedPackage: true
              }
            });
        } else {
          this.transitionToRoute("review_item.accept", offer, model);
        }
      } else {
        this.transitionToRoute("item.edit");
      }
    },

    nextWithoutImage() {
      var item = this.get("item");
      if (item) {
        if (this.get('session.isAdminApp')) {
          if(this.get("isUnplannedPackage")) {
            const packageId = this.get("item.packages.firstObject.id");
            return this.transitionToRoute("receive_package", packageId, {
              queryParams: {
                isUnplannedPackage: true
              }
            });
          }
          return this.transitionToRoute("review_item.accept", this.get('offer'), item);
        }
        else if (!item.get('isOffer')) {
          return this.transitionToRoute("item.edit", item.get("id"));
        }
      }
      const defaultDonorCondition = this.get("store").peekAll("donorCondition").sortBy("id").get("firstObject");
      this.createItem(defaultDonorCondition, true);
    },

    back() {
      if (this.get("session.isAdminApp")) {
        this.transitionToRoute("review_offer.items");
      } else {
        if (this.get('offer.itemCount') === 0) {
          this.transitionToRoute("offers");
        } else {
          this.transitionToRoute("offer.offer_details");
        }
      }
    },

    onNewItem(item) {
      if (this.get("session.isAdminApp")) {
        this.transitionToRoute("item.edit_images", item.get("id"));
      } else {
        this.transitionToRoute("item.edit", item.get("id"));
      }
    },

    setPreview(image) {
      this.get("item.images").setEach("selected", false);
      image.set("selected", true);
      this.set("previewImage", image);
    },

    setFavourite() {
      if (this.get("package")) {
        var pkg = this.get("package");
        pkg.set("favouriteImage", this.get("previewImage"));
        pkg.save()
          .catch(error => { pkg.rollbackAttributes(); throw error; });
      } else {
        this.get("item.images").setEach("favourite", false);
        this.get("previewImage").set("favourite", true);
        this.get("previewImage").save()
          .catch(error => {
            this.get("item.images").forEach(img => img.rollbackAttributes());
            throw error;
          });
      }
    },

    deleteImage() {
      if (this.get("item.images.length") === 1) {
        this.get("session.isAdminApp") ? this.confirmRemoveLastImage() :
          this.cannotRemoveImageAlert();
        return;
      } else {
        this.get("messageBox").confirm(this.get("i18n").t("edit_images.delete_confirm"), () => {
          var loadingView = getOwner(this).lookup('component:loading').append();
          var img = this.get("previewImage");
          img.deleteRecord();
          img.save()
            .then(i => {
              i.unloadRecord();
              this.initPreviewImage();
              if (!this.get("favouriteImage")) {
                this.send("setFavourite");
              }
            })
            .catch(error => { img.rollbackAttributes(); throw error; })
            .finally(() => loadingView.destroy());
        });
      }
    },

    expandImage() {
      this.toggleProperty("isExpanded");
    },

    //file upload
    triggerUpload() {

      // For Cordova application
      if (config.cordova.enabled) {
        var onSuccess = ((function(_this) {
          return function(path) {
            console.log(path);
            var dataURL = "data:image/jpg;base64," + path;

            $("input[type='file']").fileupload('option', 'formData').file = dataURL;
            $("input[type='file']").fileupload('add', { files: [dataURL] });
          };
        })(this));

        this.initActionSheet(onSuccess);
      } else {
        // On the browser
        if (this.get('item.isOffer')) {
          // On the offer page we allow the user to move to the item edition page first
          // and then upload images from there
          return this.send('nextWithoutImage');
        }
        // On the item edition page we trigger the file selection
        if (navigator.userAgent.match(/iemobile/i)) {
          //don't know why but on windows phone need to click twice in quick succession
          //for dialog to appear
          Ember.$("#photo-list input[type='file']").click().click();
        } else {
          Ember.$("#photo-list input[type='file']").trigger("click");
        }
      }
    },

    uploadReady() {
      this.set("isReady", true);
    },

    uploadStart(e, data) {
      this.set("uploadedFileDate", data);
      Ember.$(".loading-image-indicator").show();
    },

    cancelUpload() {
      if (this.get("uploadedFileDate")) { this.get("uploadedFileDate").abort(); }
    },

    uploadProgress(e, data) {
      e.target.disabled = true; // disable image-selection
      var progress = parseInt(data.loaded / data.total * 100, 10) || 0;
      this.set("addPhotoLabel", progress + "%");
      this.set("loadingPercentage", this.get("i18n").t("edit_images.image_uploading") + progress + "%");
    },

    uploadComplete(e) {
      e.target.disabled = false; // enable image-selection
      this.set("uploadedFileDate", null);
      Ember.$(".loading-image-indicator.hide_image_loading").hide();
      this.set("addPhotoLabel", this.get("i18n").t("edit_images.add_photo"));
      this.set("loadingPercentage", this.get("i18n").t("edit_images.image_uploading"));
    },

    uploadSuccess(e, data) {
      var identifier = data.result.version + "/" + data.result.public_id + "." + data.result.format;
      var item = this.get("item");
      if (!item || this.get("item.isOffer")) {
        var defaultDonorCondition = this.get("store").peekAll("donorCondition").sortBy("id").get("firstObject");
        this.createItem(defaultDonorCondition, false, identifier);
      } else {
        var favourite = item.get("images.length") === 0;
        var img = this.get("store").createRecord('image', { cloudinaryId: identifier, item: this.get("item"), favourite: favourite });
        img.save().catch(error => { img.unloadRecord(); throw error; });
      }
    },

    rotateImageRight() {
      var angle = this.get("previewImage.angle");
      angle = (angle + 90) % 360;
      this.send("rotateImage", angle);
    },

    rotateImageLeft() {
      var angle = this.get("previewImage.angle");
      angle = (angle ? (angle - 90) : 270) % 360;
      this.send("rotateImage", angle);
    },

    rotateImage(angle) {
      var image = this.get("previewImage");
      image.set("angle", angle);
      Ember.run.debounce(this, this.saveImageRotation, image, 400);
    }
  },

  saveImageRotation(image) {
    image.save();
  }

});
<|endoftext|>


<commit_before> 'use strict';

function userEventsCtrl($scope, $stateParams, $translate, cdEventsService, loggedInUser, alertService) {

  if(loggedInUser.data && $stateParams.userId){
    cdEventsService.getUserDojosEvents($stateParams.userId, function (response) {
      $scope.events = response;
      if(_.isEmpty($scope.events)) {
        //This user has no Events.
      }
    }, function (err) {
      alertService.showError( $translate.instant('Error loading Events') + ' ' + err);
    })
  }
}

angular.module('cpZenPlatform')
    .controller('user-events-controller', ['$scope', '$stateParams', '$translate', 'cdEventsService', 'loggedInUser', 'alertService', userEventsCtrl]);
<commit_msg>finished getting the events from the events-service<commit_after> 'use strict';

function userEventsCtrl($scope, $stateParams, $translate, cdEventsService, alertService) {

  if($stateParams.userId){
    cdEventsService.getUserDojosEvents($stateParams.userId, function (response) {
      $scope.events = response;
      if(_.isEmpty($scope.events)) {
        //This user has no Events.
      }
    }, function (err) {
      alertService.showError( $translate.instant('Error loading Events') + ' ' + err);
    })
  }
}

angular.module('cpZenPlatform')
    .controller('user-events-controller', ['$scope', '$stateParams', '$translate', 'cdEventsService', 'alertService', userEventsCtrl]);
<|endoftext|>


<commit_before>/* eslint-disable no-constant-condition */
import { isDev } from 'worona-deps';
import { takeEvery } from 'redux-saga';
import { fork, call, select, put } from 'redux-saga/effects';
import * as deps from '../deps';
import * as selectors from '../selectors';
import * as actions from '../actions';
import addSettings from './addSettings';
import saveSettings from './saveSettings';
import defaultSettings from './defaultSettings';

const env = isDev ? 'dev' : 'prod';

export function* requestPackages() {
  yield [
    call(deps.sagaHelpers.waitForReady, 'settings-live', selectors.getSettingsLiveIsReady),
    call(deps.sagaHelpers.waitForReady, 'packages', selectors.getPackageIsReady),
  ];
  const pkgs = yield select(selectors.getPackageCollection);
  yield pkgs.map(pkg => put(deps.actions.packageActivationRequested({ pkg })));
}

export function* addDefaultSettings({ siteId }) {
  const devPkgs = yield select(selectors.getDevPackageCollection);
  yield devPkgs.map(pkg => put(actions.addSettingsRequested({ ...pkg, siteId })));
}

export default function* settingsagas() {
  yield [
    fork(defaultSettings),
    fork(deps.sagaCreators.collectionWatcherCreator('settings-live')),
    fork(deps.sagaCreators.subscriptionWatcherCreator('settings-live')),
    fork(deps.sagaCreators.collectionWatcherCreator('packages')),
    fork(deps.sagaCreators.subscriptionWatcherCreator('packages', env)),
    fork(requestPackages),
    fork(saveSettings),
    fork(addSettings),
    takeEvery(deps.types.CREATE_SITE_SUCCEED, addDefaultSettings),
  ];
}
<commit_msg>Activate new added packages.<commit_after>/* eslint-disable no-constant-condition */
import { isDev } from 'worona-deps';
import { takeEvery } from 'redux-saga';
import { find } from 'lodash';
import { fork, call, select, put } from 'redux-saga/effects';
import * as deps from '../deps';
import * as selectors from '../selectors';
import * as actions from '../actions';
import addSettings from './addSettings';
import saveSettings from './saveSettings';
import defaultSettings from './defaultSettings';

const env = isDev ? 'dev' : 'prod';

export function* requestPackage({ fields: { name } }) {
  const pkgs = yield select(selectors.getPackageCollection);
  const pkg = find(pkgs, pkgObj => pkgObj.name === name);
  yield put(deps.actions.packageActivationRequested({ pkg }));
}

export function* requestPackages() {
  yield [
    call(deps.sagaHelpers.waitForReady, 'settings-live', selectors.getSettingsLiveIsReady),
    call(deps.sagaHelpers.waitForReady, 'packages', selectors.getPackageIsReady),
  ];
  const pkgs = yield select(selectors.getPackageCollection);
  yield pkgs.map(pkg => put(deps.actions.packageActivationRequested({ pkg })));

  yield takeEvery(({ type, collection, event }) =>
    type === deps.types.COLLECTION_MODIFIED && collection === 'packages' && event === 'added',
    requestPackage
  );
}

export function* addDefaultSettings({ siteId }) {
  const devPkgs = yield select(selectors.getDevPackageCollection);
  yield devPkgs.map(pkg => put(actions.addSettingsRequested({ ...pkg, siteId })));
}

export default function* settingsagas() {
  yield [
    fork(defaultSettings),
    fork(deps.sagaCreators.collectionWatcherCreator('settings-live')),
    fork(deps.sagaCreators.subscriptionWatcherCreator('settings-live')),
    fork(deps.sagaCreators.collectionWatcherCreator('packages')),
    fork(deps.sagaCreators.subscriptionWatcherCreator('packages', env)),
    fork(requestPackages),
    fork(saveSettings),
    fork(addSettings),
    takeEvery(deps.types.CREATE_SITE_SUCCEED, addDefaultSettings),
  ];
}
<|endoftext|>


<commit_before>/* eslint-disable ghost/ember/alias-model-in-controller */
import $ from 'jquery';
import Controller from '@ember/controller';
import generatePassword from 'ghost-admin/utils/password-generator';
import validator from 'validator';
import {
    IMAGE_EXTENSIONS,
    IMAGE_MIME_TYPES
} from 'ghost-admin/components/gh-image-uploader';
import {computed} from '@ember/object';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';

const ICON_EXTENSIONS = ['ico', 'png'];

function randomPassword() {
    let word = generatePassword(6);
    let randomN = Math.floor(Math.random() * 1000);

    return word + randomN;
}

export default Controller.extend({
    config: service(),
    ghostPaths: service(),
    notifications: service(),
    session: service(),
    settings: service(),
    ui: service(),

    availableTimezones: null,
    iconExtensions: null,
    iconMimeTypes: 'image/png,image/x-icon',
    imageExtensions: IMAGE_EXTENSIONS,
    imageMimeTypes: IMAGE_MIME_TYPES,
    _scratchFacebook: null,
    _scratchTwitter: null,

    init() {
        this._super(...arguments);
        this.iconExtensions = ICON_EXTENSIONS;
    },

    privateRSSUrl: computed('config.blogUrl', 'settings.publicHash', function () {
        let blogUrl = this.get('config.blogUrl');
        let publicHash = this.get('settings.publicHash');

        return `${blogUrl}/${publicHash}/rss`;
    }),

    actions: {
        save() {
            this.save.perform();
        },

        setTimezone(timezone) {
            this.set('settings.timezone', timezone.name);
        },

        removeImage(image) {
            // setting `null` here will error as the server treats it as "null"
            this.settings.set(image, '');
        },

        /**
         * Opens a file selection dialog - Triggered by "Upload Image" buttons,
         * searches for the hidden file input within the .gh-setting element
         * containing the clicked button then simulates a click
         * @param  {MouseEvent} event - MouseEvent fired by the button click
         */
        triggerFileDialog(event) {
            // simulate click to open file dialog
            // using jQuery because IE11 doesn't support MouseEvent
            $(event.target)
                .closest('.gh-setting-action')
                .find('input[type="file"]')
                .click();
        },

        /**
         * Fired after an image upload completes
         * @param  {string} property - Property name to be set on `this.settings`
         * @param  {UploadResult[]} results - Array of UploadResult objects
         * @return {string} The URL that was set on `this.settings.property`
         */
        imageUploaded(property, results) {
            if (results[0]) {
                return this.settings.set(property, results[0].url);
            }
        },

        toggleIsPrivate(isPrivate) {
            let settings = this.settings;

            settings.set('isPrivate', isPrivate);
            settings.get('errors').remove('password');

            let changedAttrs = settings.changedAttributes();

            // set a new random password when isPrivate is enabled
            if (isPrivate && changedAttrs.isPrivate) {
                settings.set('password', randomPassword());

            // reset the password when isPrivate is disabled
            } else if (changedAttrs.password) {
                settings.set('password', changedAttrs.password[0]);
            }
        },

        toggleLeaveSettingsModal(transition) {
            let leaveTransition = this.leaveSettingsTransition;

            if (!transition && this.showLeaveSettingsModal) {
                this.set('leaveSettingsTransition', null);
                this.set('showLeaveSettingsModal', false);
                return;
            }

            if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
                this.set('leaveSettingsTransition', transition);

                // if a save is running, wait for it to finish then transition
                if (this.save.isRunning) {
                    return this.save.last.then(() => {
                        transition.retry();
                    });
                }

                // we genuinely have unsaved data, show the modal
                this.set('showLeaveSettingsModal', true);
            }
        },

        leaveSettings() {
            let transition = this.leaveSettingsTransition;
            let settings = this.settings;

            if (!transition) {
                this.notifications.showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
                return;
            }

            // roll back changes on settings props
            settings.rollbackAttributes();

            return transition.retry();
        },

        validateFacebookUrl() {
            let newUrl = this._scratchFacebook;
            let oldUrl = this.get('settings.facebook');
            let errMessage = '';

            // reset errors and validation
            this.get('settings.errors').remove('facebook');
            this.get('settings.hasValidated').removeObject('facebook');

            if (newUrl === '') {
                // Clear out the Facebook url
                this.set('settings.facebook', '');
                return;
            }

            // _scratchFacebook will be null unless the user has input something
            if (!newUrl) {
                newUrl = oldUrl;
            }

            try {
                // strip any facebook URLs out
                newUrl = newUrl.replace(/(https?:\/\/)?(www\.)?facebook\.com/i, '');

                // don't allow any non-facebook urls
                if (newUrl.match(/^(http|\/\/)/i)) {
                    throw 'invalid url';
                }

                // strip leading / if we have one then concat to full facebook URL
                newUrl = newUrl.replace(/^\//, '');
                newUrl = `https://www.facebook.com/${newUrl}`;

                // don't allow URL if it's not valid
                if (!validator.isURL(newUrl)) {
                    throw 'invalid url';
                }

                this.set('settings.facebook', '');
                run.schedule('afterRender', this, function () {
                    this.set('settings.facebook', newUrl);
                });
            } catch (e) {
                if (e === 'invalid url') {
                    errMessage = 'The URL must be in a format like '
                               + 'https://www.facebook.com/yourPage';
                    this.get('settings.errors').add('facebook', errMessage);
                    return;
                }

                throw e;
            } finally {
                this.get('settings.hasValidated').pushObject('facebook');
            }
        },

        validateTwitterUrl() {
            let newUrl = this._scratchTwitter;
            let oldUrl = this.get('settings.twitter');
            let errMessage = '';

            // reset errors and validation
            this.get('settings.errors').remove('twitter');
            this.get('settings.hasValidated').removeObject('twitter');

            if (newUrl === '') {
                // Clear out the Twitter url
                this.set('settings.twitter', '');
                return;
            }

            // _scratchTwitter will be null unless the user has input something
            if (!newUrl) {
                newUrl = oldUrl;
            }

            if (newUrl.match(/(?:twitter\.com\/)(\S+)/) || newUrl.match(/([a-z\d.]+)/i)) {
                let username = [];

                if (newUrl.match(/(?:twitter\.com\/)(\S+)/)) {
                    [, username] = newUrl.match(/(?:twitter\.com\/)(\S+)/);
                } else {
                    [username] = newUrl.match(/([^/]+)\/?$/mi);
                }

                // check if username starts with http or www and show error if so
                if (username.match(/^(http|www)|(\/)/) || !username.match(/^[a-z\d._]{1,15}$/mi)) {
                    errMessage = !username.match(/^[a-z\d._]{1,15}$/mi) ? 'Your Username is not a valid Twitter Username' : 'The URL must be in a format like https://twitter.com/yourUsername';

                    this.get('settings.errors').add('twitter', errMessage);
                    this.get('settings.hasValidated').pushObject('twitter');
                    return;
                }

                newUrl = `https://twitter.com/${username}`;

                this.get('settings.hasValidated').pushObject('twitter');

                this.set('settings.twitter', '');
                run.schedule('afterRender', this, function () {
                    this.set('settings.twitter', newUrl);
                });
            } else {
                errMessage = 'The URL must be in a format like '
                           + 'https://twitter.com/yourUsername';
                this.get('settings.errors').add('twitter', errMessage);
                this.get('settings.hasValidated').pushObject('twitter');
                return;
            }
        }
    },

    _deleteTheme() {
        let theme = this.store.peekRecord('theme', this.themeToDelete.name);

        if (!theme) {
            return;
        }

        return theme.destroyRecord().catch((error) => {
            this.notifications.showAPIError(error);
        });
    },

    save: task(function* () {
        let notifications = this.notifications;
        let config = this.config;

        try {
            let settings = yield this.settings.save();
            config.set('blogTitle', settings.get('title'));

            // this forces the document title to recompute after a blog title change
            this.ui.updateDocumentTitle();

            return settings;
        } catch (error) {
            if (error) {
                notifications.showAPIError(error, {key: 'settings.save'});
            }
            throw error;
        }
    })
});
<commit_msg>🐛 Fixed social settings not saved on keyboard shortcut (#1633)<commit_after>/* eslint-disable ghost/ember/alias-model-in-controller */
import $ from 'jquery';
import Controller from '@ember/controller';
import generatePassword from 'ghost-admin/utils/password-generator';
import validator from 'validator';
import {
    IMAGE_EXTENSIONS,
    IMAGE_MIME_TYPES
} from 'ghost-admin/components/gh-image-uploader';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';

const ICON_EXTENSIONS = ['ico', 'png'];

function randomPassword() {
    let word = generatePassword(6);
    let randomN = Math.floor(Math.random() * 1000);

    return word + randomN;
}

export default Controller.extend({
    config: service(),
    ghostPaths: service(),
    notifications: service(),
    session: service(),
    settings: service(),
    ui: service(),

    availableTimezones: null,
    iconExtensions: null,
    iconMimeTypes: 'image/png,image/x-icon',
    imageExtensions: IMAGE_EXTENSIONS,
    imageMimeTypes: IMAGE_MIME_TYPES,
    _scratchFacebook: null,
    _scratchTwitter: null,

    init() {
        this._super(...arguments);
        this.iconExtensions = ICON_EXTENSIONS;
    },

    privateRSSUrl: computed('config.blogUrl', 'settings.publicHash', function () {
        let blogUrl = this.get('config.blogUrl');
        let publicHash = this.get('settings.publicHash');

        return `${blogUrl}/${publicHash}/rss`;
    }),

    actions: {
        save() {
            this.save.perform();
        },

        setTimezone(timezone) {
            this.set('settings.timezone', timezone.name);
        },

        removeImage(image) {
            // setting `null` here will error as the server treats it as "null"
            this.settings.set(image, '');
        },

        /**
         * Opens a file selection dialog - Triggered by "Upload Image" buttons,
         * searches for the hidden file input within the .gh-setting element
         * containing the clicked button then simulates a click
         * @param  {MouseEvent} event - MouseEvent fired by the button click
         */
        triggerFileDialog(event) {
            // simulate click to open file dialog
            // using jQuery because IE11 doesn't support MouseEvent
            $(event.target)
                .closest('.gh-setting-action')
                .find('input[type="file"]')
                .click();
        },

        /**
         * Fired after an image upload completes
         * @param  {string} property - Property name to be set on `this.settings`
         * @param  {UploadResult[]} results - Array of UploadResult objects
         * @return {string} The URL that was set on `this.settings.property`
         */
        imageUploaded(property, results) {
            if (results[0]) {
                return this.settings.set(property, results[0].url);
            }
        },

        toggleIsPrivate(isPrivate) {
            let settings = this.settings;

            settings.set('isPrivate', isPrivate);
            settings.get('errors').remove('password');

            let changedAttrs = settings.changedAttributes();

            // set a new random password when isPrivate is enabled
            if (isPrivate && changedAttrs.isPrivate) {
                settings.set('password', randomPassword());

            // reset the password when isPrivate is disabled
            } else if (changedAttrs.password) {
                settings.set('password', changedAttrs.password[0]);
            }
        },

        toggleLeaveSettingsModal(transition) {
            let leaveTransition = this.leaveSettingsTransition;

            if (!transition && this.showLeaveSettingsModal) {
                this.set('leaveSettingsTransition', null);
                this.set('showLeaveSettingsModal', false);
                return;
            }

            if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
                this.set('leaveSettingsTransition', transition);

                // if a save is running, wait for it to finish then transition
                if (this.save.isRunning) {
                    return this.save.last.then(() => {
                        transition.retry();
                    });
                }

                // we genuinely have unsaved data, show the modal
                this.set('showLeaveSettingsModal', true);
            }
        },

        leaveSettings() {
            let transition = this.leaveSettingsTransition;
            let settings = this.settings;

            if (!transition) {
                this.notifications.showAlert('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
                return;
            }

            // roll back changes on settings props
            settings.rollbackAttributes();

            return transition.retry();
        },

        validateFacebookUrl() {
            let newUrl = this._scratchFacebook;
            let oldUrl = this.get('settings.facebook');
            let errMessage = '';

            // reset errors and validation
            this.get('settings.errors').remove('facebook');
            this.get('settings.hasValidated').removeObject('facebook');

            if (newUrl === '') {
                // Clear out the Facebook url
                this.set('settings.facebook', '');
                return;
            }

            // _scratchFacebook will be null unless the user has input something
            if (!newUrl) {
                newUrl = oldUrl;
            }

            try {
                // strip any facebook URLs out
                newUrl = newUrl.replace(/(https?:\/\/)?(www\.)?facebook\.com/i, '');

                // don't allow any non-facebook urls
                if (newUrl.match(/^(http|\/\/)/i)) {
                    throw 'invalid url';
                }

                // strip leading / if we have one then concat to full facebook URL
                newUrl = newUrl.replace(/^\//, '');
                newUrl = `https://www.facebook.com/${newUrl}`;

                // don't allow URL if it's not valid
                if (!validator.isURL(newUrl)) {
                    throw 'invalid url';
                }

                this.settings.set('facebook', newUrl);
                this.settings.notifyPropertyChange('facebook');
            } catch (e) {
                if (e === 'invalid url') {
                    errMessage = 'The URL must be in a format like '
                               + 'https://www.facebook.com/yourPage';
                    this.get('settings.errors').add('facebook', errMessage);
                    return;
                }

                throw e;
            } finally {
                this.get('settings.hasValidated').pushObject('facebook');
            }
        },

        validateTwitterUrl() {
            let newUrl = this._scratchTwitter;
            let oldUrl = this.get('settings.twitter');
            let errMessage = '';

            // reset errors and validation
            this.get('settings.errors').remove('twitter');
            this.get('settings.hasValidated').removeObject('twitter');

            if (newUrl === '') {
                // Clear out the Twitter url
                this.set('settings.twitter', '');
                return;
            }

            // _scratchTwitter will be null unless the user has input something
            if (!newUrl) {
                newUrl = oldUrl;
            }

            if (newUrl.match(/(?:twitter\.com\/)(\S+)/) || newUrl.match(/([a-z\d.]+)/i)) {
                let username = [];

                if (newUrl.match(/(?:twitter\.com\/)(\S+)/)) {
                    [, username] = newUrl.match(/(?:twitter\.com\/)(\S+)/);
                } else {
                    [username] = newUrl.match(/([^/]+)\/?$/mi);
                }

                // check if username starts with http or www and show error if so
                if (username.match(/^(http|www)|(\/)/) || !username.match(/^[a-z\d._]{1,15}$/mi)) {
                    errMessage = !username.match(/^[a-z\d._]{1,15}$/mi) ? 'Your Username is not a valid Twitter Username' : 'The URL must be in a format like https://twitter.com/yourUsername';

                    this.get('settings.errors').add('twitter', errMessage);
                    this.get('settings.hasValidated').pushObject('twitter');
                    return;
                }

                newUrl = `https://twitter.com/${username}`;

                this.settings.get('hasValidated').pushObject('twitter');
                this.settings.set('twitter', newUrl);
                this.settings.notifyPropertyChange('twitter');
            } else {
                errMessage = 'The URL must be in a format like '
                           + 'https://twitter.com/yourUsername';
                this.get('settings.errors').add('twitter', errMessage);
                this.get('settings.hasValidated').pushObject('twitter');
                return;
            }
        }
    },

    _deleteTheme() {
        let theme = this.store.peekRecord('theme', this.themeToDelete.name);

        if (!theme) {
            return;
        }

        return theme.destroyRecord().catch((error) => {
            this.notifications.showAPIError(error);
        });
    },

    save: task(function* () {
        let notifications = this.notifications;
        let config = this.config;

        if (this.settings.get('twitter') !== this._scratchTwitter) {
            this.send('validateTwitterUrl');
        }

        if (this.settings.get('facebook') !== this._scratchFacebook) {
            this.send('validateFacebookUrl');
        }

        try {
            let settings = yield this.settings.save();
            config.set('blogTitle', settings.get('title'));

            // this forces the document title to recompute after a blog title change
            this.ui.updateDocumentTitle();

            return settings;
        } catch (error) {
            if (error) {
                notifications.showAPIError(error, {key: 'settings.save'});
            }
            throw error;
        }
    })
});
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';

import {inject} from 'mobx-react';

@inject(({store}) => ({onAdd: store.saveGreeting}))
export default class GreetingDetail extends React.Component {
    render() {
        const {name, greeting} = this.state;
        const saveDisabled = !(name && greeting);

        return (

                <input ref={input => this.input = input}
                       onChange={event => this.updateModel('name', event.target.value)}
                       value={name}
                       placeholder="Name"/>
                <input onChange={event => this.updateModel('greeting', event.target.value)}
                       value={greeting}
                       placeholder="Greeting"/>
                <button
                    onClick={() => this.reset()}>
                    Clear
                </button>
                <button
                    disabled={saveDisabled}
                    onClick={() => this.save()}>
                    Save
                </button>

);
    }

    constructor(props) {
        super(props);
        const {name, greeting} = this.props.greeting || {name: '', greeting: ''};
        this.state = {
            name,
            greeting
        };
    }

    reset() {
        this.setState({name: '', greeting: ''});
        this.input.focus();
    }

    save() {
        const {onAdd} = this.props;
        const {name, greeting} = this.state;
        onAdd({
            name,
            greeting
        });
    }

    updateModel(name, value) {
        this.setState({[name]: value});
    }
}
GreetingDetail.wrappedComponent.propTypes = {
    greeting: PropTypes.shape({
        name: PropTypes.string.isRequired,
        greeting: PropTypes.string.isRequired
    }),
    onAdd: PropTypes.func.isRequired
};
<commit_msg>Align GreeingDetail with other steps<commit_after>import React, {PropTypes} from 'react';

import {inject} from 'mobx-react';

@inject(({store}) => ({onAdd: store.saveGreeting}))
export default class GreetingDetail extends React.Component {
    render() {
        const {name, greeting} = this.state;
        const saveDisabled = !(name && greeting);

        return (

                <input ref={input => this.input = input}
                       onChange={event => this.updateModel(event)}
                       name="name"
                       value={name}
                       placeholder="Name"/>
                <input onChange={event => this.updateModel(event)}
                       name="greeting"
                       value={greeting}
                       placeholder="Greeting"/>
                <button
                    onClick={() => this.reset()}>
                    Clear
                </button>
                <button
                    disabled={saveDisabled}
                    onClick={() => this.save()}>
                    Save
                </button>

);
    }

    constructor(props) {
        super(props);
        const {name, greeting} = this.props.greeting || {name: '', greeting: ''};
        this.state = {
            name,
            greeting
        };
    }

    reset() {
        this.setState({name: '', greeting: ''});
        if (this.input) {
            this.input.focus();
        }
    }

    save() {
        const {onAdd} = this.props;
        const {name, greeting} = this.state;
        onAdd({
            name,
            greeting
        });
    }

    updateModel(event) {
        this.setState({[event.target.name]: event.target.value});
    }
}
GreetingDetail.wrappedComponent.propTypes = {
    greeting: PropTypes.shape({
        name: PropTypes.string.isRequired,
        greeting: PropTypes.string.isRequired
    }),
    onAdd: PropTypes.func.isRequired
};
<|endoftext|>


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

/**
 * @fileoverview Controllers for the exploration statistics tab in the
 * exploration editor.
 */

oppia.constant('IMPROVE_TYPE_INCOMPLETE', 'incomplete');

oppia.controller('StatisticsTab', [
  '$scope', '$http', '$modal', 'AlertsService', 'explorationStatesService',
  'ExplorationDataService', 'computeGraphService', 'oppiaDatetimeFormatter',
  'StatesObjectFactory', 'StateImprovementSuggestionService',
  'ReadOnlyExplorationBackendApiService', 'UrlInterpolationService',
  'IMPROVE_TYPE_INCOMPLETE', 'ENABLE_NEW_STATS_FRAMEWORK',
  function(
      $scope, $http, $modal, AlertsService, explorationStatesService,
      ExplorationDataService, computeGraphService, oppiaDatetimeFormatter,
      StatesObjectFactory, StateImprovementSuggestionService,
      ReadOnlyExplorationBackendApiService, UrlInterpolationService,
      IMPROVE_TYPE_INCOMPLETE, ENABLE_NEW_STATS_FRAMEWORK) {
    $scope.COMPLETION_RATE_CHART_OPTIONS = {
      chartAreaWidth: 300,
      colors: ['green', 'firebrick'],
      height: 100,
      legendPosition: 'right',
      width: 500
    };
    $scope.COMPLETION_RATE_PIE_CHART_OPTIONS = {
      title: '',
      left: 230,
      pieHole: 0.6,
      pieSliceTextStyleColor: 'black',
      pieSliceBorderColor: 'black',
      chartAreaWidth: 500,
      colors: ['#d8d8d8', '#008808', 'blue'],
      height: 300,
      legendPosition: 'right',
      width: 600
    };
    var _EXPLORATION_STATS_VERSION_ALL = 'all';
    $scope.currentVersion = _EXPLORATION_STATS_VERSION_ALL;

    $scope.getLocaleAbbreviatedDatetimeString = function(millisSinceEpoch) {
      return oppiaDatetimeFormatter.getLocaleAbbreviatedDatetimeString(
        millisSinceEpoch);
    };

    $scope.ENABLE_NEW_STATS_FRAMEWORK = ENABLE_NEW_STATS_FRAMEWORK;
    $scope.hasTabLoaded = false;
    $scope.$on('refreshStatisticsTab', function() {
      $scope.refreshExplorationStatistics(_EXPLORATION_STATS_VERSION_ALL);
      $scope.explorationVersionUrl = (
        '/createhandler/statisticsversion/' +
        ExplorationDataService.explorationId);
      $http.get($scope.explorationVersionUrl).then(function(response) {
        $scope.versions = response.data.versions;
        $scope.currentVersion = _EXPLORATION_STATS_VERSION_ALL;
      });
    });

    $scope.explorationHasBeenVisited = false;
    $scope.refreshExplorationStatistics = function(version) {
      if ($scope.ENABLE_NEW_STATS_FRAMEWORK) {
        $scope.explorationStatisticsUrl = (
          '/createhandler/statistics/' + ExplorationDataService.explorationId);
      } else {
        $scope.explorationStatisticsUrl = (
          '/createhandler/statistics_old/' +
          ExplorationDataService.explorationId + '/' + version);
      }

      $http.get($scope.explorationStatisticsUrl).then(function(response) {
        ReadOnlyExplorationBackendApiService.loadLatestExploration(
          ExplorationDataService.explorationId).then(function(response) {
            var statesDict = response.exploration.states;
            var states = StatesObjectFactory.createFromBackendDict(statesDict);
            var initStateName = response.exploration.init_state_name;

            $scope.statsGraphData = computeGraphService.compute(
              initStateName, states);
            var improvements = (
              StateImprovementSuggestionService.getStateImprovements(
                states, $scope.stateStats));
            $scope.highlightStates = {};
            improvements.forEach(function(impItem) {
              // TODO(bhenning): This is the feedback for improvement types
              // and should be included with the definitions of the
              // improvement types.
              if (impItem.type === IMPROVE_TYPE_INCOMPLETE) {
                $scope.highlightStates[impItem.stateName] = (
                  'May be confusing');
              }
            });
          }
        );
        // From this point on, all computation done is for the new stats
        // framework under a development flag.
        if ($scope.ENABLE_NEW_STATS_FRAMEWORK) {
          var data = response.data;
          var numStarts = data.num_starts;
          var numActualStarts = data.num_actual_starts;
          var numCompletions = data.num_completions;
          $scope.stateStats = data.state_stats_mapping;

          if (numStarts > 0) {
            $scope.explorationHasBeenVisited = true;
          }

          $scope.pieChartData = [
            ['Type', 'Number'],
            ['Passerby', numStarts - numActualStarts],
            ['Completions', numCompletions],
            ['Non-Completions', numActualStarts - numCompletions]
          ];
        } else {
          var data = response.data;
          var numVisits = data.num_starts;
          var numCompletions = data.num_completions;
          var improvements = data.improvements;
          $scope.stateStats = data.state_stats;
          $scope.lastUpdated = data.last_updated;

          if (numVisits > 0) {
            $scope.explorationHasBeenVisited = true;
          }

          $scope.chartData = [
            ['', 'Completions', 'Non-completions'],
            ['', numCompletions, numVisits - numCompletions]
          ];

          $scope.statsGraphOpacities = {
            legend: 'Students entering state'
          };
          // TODO(bhenning): before, there was a special opacity computed for
          // the ending (numCompletions/numVisits), should we do this for all
          // terminal nodes, instead? If so, explorationStatesService needs to
          // be able to provide whether given states are terminal

          explorationStatesService.getStateNames().forEach(function(stateName) {
            var visits = 0;
            if ($scope.stateStats.hasOwnProperty(stateName)) {
              visits = $scope.stateStats[stateName].first_entry_count;
            }
            $scope.statsGraphOpacities[stateName] = Math.max(
              visits / numVisits, 0.05);
          });

          $scope.hasTabLoaded = true;
        }
      });
    };

    $scope.onClickStateInStatsGraph = function(stateName) {
      $scope.showStateStatsModal(stateName, $scope.highlightStates[stateName]);
    };

    $scope.showStateStatsModal = function(stateName, improvementType) {
      AlertsService.clearWarnings();

      $http.get(
        '/createhandler/state_rules_stats/' + $scope.explorationId + '/' +
        encodeURIComponent(stateName)
      ).then(function(response) {
        $modal.open({
          templateUrl: UrlInterpolationService.getDirectiveTemplateUrl(
            '/pages/exploration_editor/statistics_tab/' +
            'state_stats_modal_directive.html'),
          backdrop: true,
          resolve: {
            stateName: function() {
              return stateName;
            },
            stateStats: function() {
              return $scope.stateStats[stateName];
            },
            improvementType: function() {
              return improvementType;
            },
            visualizationsInfo: function() {
              return response.data.visualizations_info;
            }
          },
          controller: [
            '$scope', '$modalInstance', '$filter', 'stateName', 'stateStats',
            'improvementType', 'visualizationsInfo', 'HtmlEscaperService',
            'SolutionVerificationService', 'ENABLE_NEW_STATS_FRAMEWORK',
            function($scope, $modalInstance, $filter, stateName, stateStats,
                improvementType, visualizationsInfo, HtmlEscaperService,
                SolutionVerificationService, ENABLE_NEW_STATS_FRAMEWORK) {
              var COMPLETION_RATE_PIE_CHART_OPTIONS = {
                left: 20,
                pieHole: 0.6,
                pieSliceTextStyleColor: 'black',
                pieSliceBorderColor: 'black',
                chartAreaWidth: 240,
                colors: ['#d8d8d8', '#008808', 'blue'],
                height: 270,
                legendPosition: 'right',
                width: 240
              };

              var title1 = 'Answer feedback statistics';
              $scope.COMPLETION_RATE_PIE_CHART_OPTIONS1 = angular.copy(
                COMPLETION_RATE_PIE_CHART_OPTIONS);
              $scope.COMPLETION_RATE_PIE_CHART_OPTIONS1.title = title1

              var title2 = 'Solution usage statistics';
              $scope.COMPLETION_RATE_PIE_CHART_OPTIONS2 = angular.copy(
                COMPLETION_RATE_PIE_CHART_OPTIONS);
              $scope.COMPLETION_RATE_PIE_CHART_OPTIONS2.title = title2

              $scope.stateName = stateName;
              $scope.stateStats = stateStats;
              $scope.improvementType = improvementType;

              $scope.ENABLE_NEW_STATS_FRAMEWORK = ENABLE_NEW_STATS_FRAMEWORK;

              var usefulFeedbackCount = (
                $scope.stateStats.useful_feedback_count);
              var totalAnswersCount = (
                $scope.stateStats.total_answers_count);
              if (totalAnswersCount > 0) {
                $scope.hasExplorationBeenAnswered = true;
              }
              $scope.pieChartData1 = [
                ['Type', 'Number'],
                ['Default feedback', totalAnswersCount - usefulFeedbackCount],
                ['Useful feedback', usefulFeedbackCount],
              ];

              var numTimesSolutionViewed = (
                $scope.stateStats.num_times_solution_viewed);
              $scope.pieChartData2 = [
                ['Type', 'Number'],
                ['Solutions used to answer', numTimesSolutionViewed],
                ['Solutions not used', totalAnswersCount - (
                  numTimesSolutionViewed)]
              ];

              var _getVisualizationsHtml = function() {
                htmlSnippets = visualizationsInfo.map(
                  function(visualizationInfo) {
                    var escapedData = HtmlEscaperService.objToEscapedJson(
                      visualizationInfo.data);
                    var escapedOptions = HtmlEscaperService.objToEscapedJson(
                      visualizationInfo.options);
                    var escapedIsAddressedResults =
                      HtmlEscaperService.objToEscapedJson(
                        visualizationInfo.data.map(function(datum) {
                          return SolutionVerificationService.verifySolution(
                            ExplorationDataService.explorationId,
                            explorationStatesService.getState($scope.stateName),
                            HtmlEscaperService.objToEscapedJson(datum));
                        }));

                    var el = $('<oppia-visualization-' +
                      $filter('camelCaseToHyphens')(visualizationInfo.id) + '/>');
                    el.attr('data', escapedData);
                    el.attr('options', escapedOptions);
                    el.attr('is-addressed', escapedIsAddressedResults);
                    return el.get(0).outerHTML;
                  });

                return htmlSnippets.join('');
              };

              $scope.visualizationsHtml = _getVisualizationsHtml();

              $scope.cancel = function() {
                $modalInstance.dismiss('cancel');
                AlertsService.clearWarnings();
              };
            }
          ]
        });
      });
    };
  }
]);
<commit_msg>Fix max-len issue.<commit_after>// Copyright 2014 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Controllers for the exploration statistics tab in the
 * exploration editor.
 */

oppia.constant('IMPROVE_TYPE_INCOMPLETE', 'incomplete');

oppia.controller('StatisticsTab', [
  '$scope', '$http', '$modal', 'AlertsService', 'explorationStatesService',
  'ExplorationDataService', 'computeGraphService', 'oppiaDatetimeFormatter',
  'StatesObjectFactory', 'StateImprovementSuggestionService',
  'ReadOnlyExplorationBackendApiService', 'UrlInterpolationService',
  'IMPROVE_TYPE_INCOMPLETE', 'ENABLE_NEW_STATS_FRAMEWORK',
  function(
      $scope, $http, $modal, AlertsService, explorationStatesService,
      ExplorationDataService, computeGraphService, oppiaDatetimeFormatter,
      StatesObjectFactory, StateImprovementSuggestionService,
      ReadOnlyExplorationBackendApiService, UrlInterpolationService,
      IMPROVE_TYPE_INCOMPLETE, ENABLE_NEW_STATS_FRAMEWORK) {
    $scope.COMPLETION_RATE_CHART_OPTIONS = {
      chartAreaWidth: 300,
      colors: ['green', 'firebrick'],
      height: 100,
      legendPosition: 'right',
      width: 500
    };
    $scope.COMPLETION_RATE_PIE_CHART_OPTIONS = {
      title: '',
      left: 230,
      pieHole: 0.6,
      pieSliceTextStyleColor: 'black',
      pieSliceBorderColor: 'black',
      chartAreaWidth: 500,
      colors: ['#d8d8d8', '#008808', 'blue'],
      height: 300,
      legendPosition: 'right',
      width: 600
    };
    var _EXPLORATION_STATS_VERSION_ALL = 'all';
    $scope.currentVersion = _EXPLORATION_STATS_VERSION_ALL;

    $scope.getLocaleAbbreviatedDatetimeString = function(millisSinceEpoch) {
      return oppiaDatetimeFormatter.getLocaleAbbreviatedDatetimeString(
        millisSinceEpoch);
    };

    $scope.ENABLE_NEW_STATS_FRAMEWORK = ENABLE_NEW_STATS_FRAMEWORK;
    $scope.hasTabLoaded = false;
    $scope.$on('refreshStatisticsTab', function() {
      $scope.refreshExplorationStatistics(_EXPLORATION_STATS_VERSION_ALL);
      $scope.explorationVersionUrl = (
        '/createhandler/statisticsversion/' +
        ExplorationDataService.explorationId);
      $http.get($scope.explorationVersionUrl).then(function(response) {
        $scope.versions = response.data.versions;
        $scope.currentVersion = _EXPLORATION_STATS_VERSION_ALL;
      });
    });

    $scope.explorationHasBeenVisited = false;
    $scope.refreshExplorationStatistics = function(version) {
      if ($scope.ENABLE_NEW_STATS_FRAMEWORK) {
        $scope.explorationStatisticsUrl = (
          '/createhandler/statistics/' + ExplorationDataService.explorationId);
      } else {
        $scope.explorationStatisticsUrl = (
          '/createhandler/statistics_old/' +
          ExplorationDataService.explorationId + '/' + version);
      }

      $http.get($scope.explorationStatisticsUrl).then(function(response) {
        ReadOnlyExplorationBackendApiService.loadLatestExploration(
          ExplorationDataService.explorationId).then(function(response) {
            var statesDict = response.exploration.states;
            var states = StatesObjectFactory.createFromBackendDict(statesDict);
            var initStateName = response.exploration.init_state_name;

            $scope.statsGraphData = computeGraphService.compute(
              initStateName, states);
            var improvements = (
              StateImprovementSuggestionService.getStateImprovements(
                states, $scope.stateStats));
            $scope.highlightStates = {};
            improvements.forEach(function(impItem) {
              // TODO(bhenning): This is the feedback for improvement types
              // and should be included with the definitions of the
              // improvement types.
              if (impItem.type === IMPROVE_TYPE_INCOMPLETE) {
                $scope.highlightStates[impItem.stateName] = (
                  'May be confusing');
              }
            });
          }
        );
        // From this point on, all computation done is for the new stats
        // framework under a development flag.
        if ($scope.ENABLE_NEW_STATS_FRAMEWORK) {
          var data = response.data;
          var numStarts = data.num_starts;
          var numActualStarts = data.num_actual_starts;
          var numCompletions = data.num_completions;
          $scope.stateStats = data.state_stats_mapping;

          if (numStarts > 0) {
            $scope.explorationHasBeenVisited = true;
          }

          $scope.pieChartData = [
            ['Type', 'Number'],
            ['Passerby', numStarts - numActualStarts],
            ['Completions', numCompletions],
            ['Non-Completions', numActualStarts - numCompletions]
          ];
        } else {
          var data = response.data;
          var numVisits = data.num_starts;
          var numCompletions = data.num_completions;
          var improvements = data.improvements;
          $scope.stateStats = data.state_stats;
          $scope.lastUpdated = data.last_updated;

          if (numVisits > 0) {
            $scope.explorationHasBeenVisited = true;
          }

          $scope.chartData = [
            ['', 'Completions', 'Non-completions'],
            ['', numCompletions, numVisits - numCompletions]
          ];

          $scope.statsGraphOpacities = {
            legend: 'Students entering state'
          };
          // TODO(bhenning): before, there was a special opacity computed for
          // the ending (numCompletions/numVisits), should we do this for all
          // terminal nodes, instead? If so, explorationStatesService needs to
          // be able to provide whether given states are terminal

          explorationStatesService.getStateNames().forEach(function(stateName) {
            var visits = 0;
            if ($scope.stateStats.hasOwnProperty(stateName)) {
              visits = $scope.stateStats[stateName].first_entry_count;
            }
            $scope.statsGraphOpacities[stateName] = Math.max(
              visits / numVisits, 0.05);
          });

          $scope.hasTabLoaded = true;
        }
      });
    };

    $scope.onClickStateInStatsGraph = function(stateName) {
      $scope.showStateStatsModal(stateName, $scope.highlightStates[stateName]);
    };

    $scope.showStateStatsModal = function(stateName, improvementType) {
      AlertsService.clearWarnings();

      $http.get(
        '/createhandler/state_rules_stats/' + $scope.explorationId + '/' +
        encodeURIComponent(stateName)
      ).then(function(response) {
        $modal.open({
          templateUrl: UrlInterpolationService.getDirectiveTemplateUrl(
            '/pages/exploration_editor/statistics_tab/' +
            'state_stats_modal_directive.html'),
          backdrop: true,
          resolve: {
            stateName: function() {
              return stateName;
            },
            stateStats: function() {
              return $scope.stateStats[stateName];
            },
            improvementType: function() {
              return improvementType;
            },
            visualizationsInfo: function() {
              return response.data.visualizations_info;
            }
          },
          controller: [
            '$scope', '$modalInstance', '$filter', 'stateName', 'stateStats',
            'improvementType', 'visualizationsInfo', 'HtmlEscaperService',
            'SolutionVerificationService', 'ENABLE_NEW_STATS_FRAMEWORK',
            function($scope, $modalInstance, $filter, stateName, stateStats,
                improvementType, visualizationsInfo, HtmlEscaperService,
                SolutionVerificationService, ENABLE_NEW_STATS_FRAMEWORK) {
              var COMPLETION_RATE_PIE_CHART_OPTIONS = {
                left: 20,
                pieHole: 0.6,
                pieSliceTextStyleColor: 'black',
                pieSliceBorderColor: 'black',
                chartAreaWidth: 240,
                colors: ['#d8d8d8', '#008808', 'blue'],
                height: 270,
                legendPosition: 'right',
                width: 240
              };

              var title1 = 'Answer feedback statistics';
              $scope.COMPLETION_RATE_PIE_CHART_OPTIONS1 = angular.copy(
                COMPLETION_RATE_PIE_CHART_OPTIONS);
              $scope.COMPLETION_RATE_PIE_CHART_OPTIONS1.title = title1

              var title2 = 'Solution usage statistics';
              $scope.COMPLETION_RATE_PIE_CHART_OPTIONS2 = angular.copy(
                COMPLETION_RATE_PIE_CHART_OPTIONS);
              $scope.COMPLETION_RATE_PIE_CHART_OPTIONS2.title = title2

              $scope.stateName = stateName;
              $scope.stateStats = stateStats;
              $scope.improvementType = improvementType;

              $scope.ENABLE_NEW_STATS_FRAMEWORK = ENABLE_NEW_STATS_FRAMEWORK;

              var usefulFeedbackCount = (
                $scope.stateStats.useful_feedback_count);
              var totalAnswersCount = (
                $scope.stateStats.total_answers_count);
              if (totalAnswersCount > 0) {
                $scope.hasExplorationBeenAnswered = true;
              }
              $scope.pieChartData1 = [
                ['Type', 'Number'],
                ['Default feedback', totalAnswersCount - usefulFeedbackCount],
                ['Useful feedback', usefulFeedbackCount],
              ];

              var numTimesSolutionViewed = (
                $scope.stateStats.num_times_solution_viewed);
              $scope.pieChartData2 = [
                ['Type', 'Number'],
                ['Solutions used to answer', numTimesSolutionViewed],
                ['Solutions not used', totalAnswersCount - (
                  numTimesSolutionViewed)]
              ];

              var _getVisualizationsHtml = function() {
                htmlSnippets = visualizationsInfo.map(
                  function(visualizationInfo) {
                    var escapedData = HtmlEscaperService.objToEscapedJson(
                      visualizationInfo.data);
                    var escapedOptions = HtmlEscaperService.objToEscapedJson(
                      visualizationInfo.options);
                    var escapedIsAddressedResults =
                      HtmlEscaperService.objToEscapedJson(
                        visualizationInfo.data.map(function(datum) {
                          return SolutionVerificationService.verifySolution(
                            ExplorationDataService.explorationId,
                            explorationStatesService.getState($scope.stateName),
                            HtmlEscaperService.objToEscapedJson(datum));
                        }));

                    var el = $(
                      '<oppia-visualization-' +
                      $filter('camelCaseToHyphens')(visualizationInfo.id) +
                      '/>');
                    el.attr('data', escapedData);
                    el.attr('options', escapedOptions);
                    el.attr('is-addressed', escapedIsAddressedResults);
                    return el.get(0).outerHTML;
                  });

                return htmlSnippets.join('');
              };

              $scope.visualizationsHtml = _getVisualizationsHtml();

              $scope.cancel = function() {
                $modalInstance.dismiss('cancel');
                AlertsService.clearWarnings();
              };
            }
          ]
        });
      });
    };
  }
]);
<|endoftext|>