From 3cbb32cc2032d81a0f12b1de1ad8199d863855d4 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 26 Jun 2014 17:07:12 -0400 Subject: [PATCH] REFACTOR: uploading avatar should share code with upload component --- .../components/avatar-uploader.js.es6 | 30 +++++++ .../components/image-uploader.js.es6 | 51 ++--------- .../discourse/mixins/upload.js.es6 | 54 ++++++++++++ .../javascripts/discourse/models/user.js | 1 - .../components/avatar-uploader.js.handlebars | 10 +++ .../modal/avatar_selector.js.handlebars | 15 +--- .../views/modal/avatar_selector_view.js | 84 ------------------- 7 files changed, 104 insertions(+), 141 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/avatar-uploader.js.es6 create mode 100644 app/assets/javascripts/discourse/mixins/upload.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/avatar-uploader.js.handlebars diff --git a/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 b/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 new file mode 100644 index 00000000000..353fb5a4c5b --- /dev/null +++ b/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 @@ -0,0 +1,30 @@ +import UploadMixin from 'discourse/mixins/upload'; + +export default Em.Component.extend(UploadMixin, { + tagName: 'span', + imageIsNotASquare: false, + type: 'avatar', + + uploadUrl: Discourse.computed.url('username', '/users/%@/preferences/user_image'), + + uploadButtonText: function() { + return this.get("uploading") ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture"); + }.property("uploading"), + + uploadDone: function(data) { + var self = this; + + // indicates the users is using an uploaded avatar + this.set("custom_avatar_upload_id", data.result.upload_id); + + // display a warning whenever the image is not a square + this.set("imageIsNotASquare", data.result.width !== data.result.height); + // in order to be as much responsive as possible, we're cheating a bit here + // indeed, the server gives us back the url to the file we've just uploaded + // often, this file is not a square, so we need to crop it properly + // this will also capture the first frame of animated avatars when they're not allowed + Discourse.Utilities.cropAvatar(data.result.url, data.files[0].type).then(function(avatarTemplate) { + self.set("uploadedAvatarTemplate", avatarTemplate); + }); + } +}); diff --git a/app/assets/javascripts/discourse/components/image-uploader.js.es6 b/app/assets/javascripts/discourse/components/image-uploader.js.es6 index 5db2fd235d2..095bf6cabe8 100644 --- a/app/assets/javascripts/discourse/components/image-uploader.js.es6 +++ b/app/assets/javascripts/discourse/components/image-uploader.js.es6 @@ -1,6 +1,6 @@ -export default Em.Component.extend({ - uploading: false, - uploadProgress: 0, +import UploadMixin from 'discourse/mixins/upload'; + +export default Em.Component.extend(UploadMixin, { backgroundStyle: function() { var imageUrl = this.get('imageUrl'); @@ -9,50 +9,11 @@ export default Em.Component.extend({ return "background-image: url(" + imageUrl + ")"; }.property('imageUrl'), - _initializeUploader: function() { - var $upload = this.$('input[type=file]'), // note: we can't cache this as fileupload replaces the input after upload - self = this; - - $upload.fileupload({ - url: this.get('uploadUrl'), - dataType: "json", - fileInput: $upload, - formData: { image_type: this.get('type') } - }); - - $upload.on('fileuploadsubmit', function (e, data) { - var result = Discourse.Utilities.validateUploadedFiles(data.files, true); - self.setProperties({ uploadProgress: 0, uploading: result }); - return result; - }); - $upload.on("fileuploadprogressall", function(e, data) { - var progress = parseInt(data.loaded / data.total * 100, 10); - self.set("uploadProgress", progress); - }); - $upload.on("fileuploaddone", function(e, data) { - if(data.result.url) { - self.set('imageUrl', data.result.url); - } else { - bootbox.alert(I18n.t('post.errors.upload')); - } - }); - $upload.on("fileuploadfail", function(e, data) { - Discourse.Utilities.displayErrorForUpload(data); - }); - $upload.on("fileuploadalways", function() { - self.setProperties({ uploading: false, uploadProgress: 0}); - }); - }.on('didInsertElement'), - - _destroyUploader: function() { - this.$('input[type=file]').fileupload('destroy'); - }.on('willDestroyElement'), + uploadDone: function(data) { + this.set('imageUrl', data.result.url); + }, actions: { - selectFile: function() { - this.$('input[type=file]').click(); - }, - trash: function() { this.set('imageUrl', null); this.sendAction('clear'); diff --git a/app/assets/javascripts/discourse/mixins/upload.js.es6 b/app/assets/javascripts/discourse/mixins/upload.js.es6 new file mode 100644 index 00000000000..58b864801b8 --- /dev/null +++ b/app/assets/javascripts/discourse/mixins/upload.js.es6 @@ -0,0 +1,54 @@ +export default Em.Mixin.create({ + uploading: false, + uploadProgress: 0, + + uploadDone: function() { + Em.warn("You should implement `uploadDone`"); + }, + + _initializeUploader: function() { + var $upload = this.$('input[type=file]'), // note: we can't cache this as fileupload replaces the input after upload + self = this; + + $upload.fileupload({ + url: this.get('uploadUrl'), + dataType: "json", + fileInput: $upload, + formData: { image_type: this.get('type') }, + pasteZone: this.$() + }); + + $upload.on('fileuploadsubmit', function (e, data) { + var result = Discourse.Utilities.validateUploadedFiles(data.files, true); + self.setProperties({ uploadProgress: 0, uploading: result }); + return result; + }); + $upload.on("fileuploadprogressall", function(e, data) { + var progress = parseInt(data.loaded / data.total * 100, 10); + self.set("uploadProgress", progress); + }); + $upload.on("fileuploaddone", function(e, data) { + if(data.result.url) { + self.uploadDone(data); + } else { + bootbox.alert(I18n.t('post.errors.upload')); + } + }); + $upload.on("fileuploadfail", function(e, data) { + Discourse.Utilities.displayErrorForUpload(data); + }); + $upload.on("fileuploadalways", function() { + self.setProperties({ uploading: false, uploadProgress: 0}); + }); + }.on('didInsertElement'), + + _destroyUploader: function() { + this.$('input[type=file]').fileupload('destroy'); + }.on('willDestroyElement'), + + actions: { + selectFile: function() { + this.$('input[type=file]').click(); + } + } +}); diff --git a/app/assets/javascripts/discourse/models/user.js b/app/assets/javascripts/discourse/models/user.js index bc01551b742..23c19acdb16 100644 --- a/app/assets/javascripts/discourse/models/user.js +++ b/app/assets/javascripts/discourse/models/user.js @@ -350,7 +350,6 @@ Discourse.User = Discourse.Model.extend({ @returns {Promise} the result of the clear profile background request */ clearProfileBackground: function() { - var user = this; return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/profile_background/clear", { type: 'PUT', data: { } diff --git a/app/assets/javascripts/discourse/templates/components/avatar-uploader.js.handlebars b/app/assets/javascripts/discourse/templates/components/avatar-uploader.js.handlebars new file mode 100644 index 00000000000..287071b98f2 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/avatar-uploader.js.handlebars @@ -0,0 +1,10 @@ + + +{{#if uploading}} + {{i18n upload_selector.uploading}} {{view.uploadProgress}}% +{{/if}} +{{#if imageIsNotASquare}} +
{{i18n user.change_avatar.image_is_not_a_square}}
+{{/if}} diff --git a/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars b/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars index e286dfb81c1..d48667cde59 100644 --- a/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars +++ b/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars @@ -7,7 +7,7 @@
- +
@@ -22,16 +22,9 @@ {{i18n user.change_avatar.uploaded_avatar_empty}} {{/if}} - - - {{#if view.uploading}} - {{i18n upload_selector.uploading}} {{view.uploadProgress}}% - {{/if}} - {{#if view.imageIsNotASquare}} -
{{i18n user.change_avatar.image_is_not_a_square}}
- {{/if}} + {{avatar-uploader username=username + uploadedAvatarTemplate=view.uploadedAvatarTemplate + custom_avatar_upload_id=controller.custom_avatar_upload_id}}
diff --git a/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js b/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js index 25dcb912c15..993dcce2c60 100644 --- a/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js +++ b/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js @@ -10,90 +10,10 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({ templateName: 'modal/avatar_selector', classNames: ['avatar-selector'], title: I18n.t('user.change_avatar.title'), - uploading: false, - uploadProgress: 0, saveDisabled: false, gravatarRefreshEnabled: Em.computed.not('controller.gravatarRefreshDisabled'), - imageIsNotASquare : false, - hasUploadedAvatar: Em.computed.or('uploadedAvatarTemplate', 'controller.custom_avatar_upload_id'), - didInsertElement: function() { - var self = this; - var $upload = $("#avatar-input"); - - this._super(); - - // simulate a click on the hidden file input when clicking on our fake file input - $("#fake-avatar-input").on("click", function(e) { - // do *NOT* use the cached `$upload` variable, because fileupload is cloning & replacing the input - // cf. https://github.com/blueimp/jQuery-File-Upload/wiki/Frequently-Asked-Questions#why-is-the-file-input-field-cloned-and-replaced-after-each-selection - $("#avatar-input").click(); - e.preventDefault(); - }); - - // define the upload endpoint - $upload.fileupload({ - url: Discourse.getURL("/users/" + this.get("controller.username") + "/preferences/user_image"), - dataType: "json", - fileInput: $upload, - formData: { image_type: "avatar" } - }); - - // when a file has been selected - $upload.on('fileuploadsubmit', function (e, data) { - var result = Discourse.Utilities.validateUploadedFiles(data.files, true); - self.setProperties({ - uploadProgress: 0, - uploading: result, - imageIsNotASquare: false - }); - return result; - }); - - // when there is a progression for the upload - $upload.on("fileuploadprogressall", function (e, data) { - var progress = parseInt(data.loaded / data.total * 100, 10); - self.set("uploadProgress", progress); - }); - - // when the upload is successful - $upload.on("fileuploaddone", function (e, data) { - // make sure we have a url - if (data.result.url) { - // indicates the users is using an uploaded avatar - self.set("controller.custom_avatar_upload_id", data.result.upload_id); - - // display a warning whenever the image is not a square - self.set("imageIsNotASquare", data.result.width !== data.result.height); - // in order to be as much responsive as possible, we're cheating a bit here - // indeed, the server gives us back the url to the file we've just uploaded - // often, this file is not a square, so we need to crop it properly - // this will also capture the first frame of animated avatars when they're not allowed - Discourse.Utilities.cropAvatar(data.result.url, data.files[0].type).then(function(avatarTemplate) { - self.set("uploadedAvatarTemplate", avatarTemplate); - }); - } else { - bootbox.alert(I18n.t('post.errors.upload')); - } - }); - - // when there has been an error with the upload - $upload.on("fileuploadfail", function (e, data) { - Discourse.Utilities.displayErrorForUpload(data); - }); - - // when the upload is done - $upload.on("fileuploadalways", function () { - self.setProperties({ uploading: false, uploadProgress: 0 }); - }); - }, - - willDestroyElement: function() { - $("#fake-avatar-input").off("click"); - $("#avatar-input").fileupload("destroy"); - }, - // *HACK* used to select the proper radio button, cause {{action}} // stops the default behavior selectedChanged: function() { @@ -104,8 +24,4 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({ }); }.observes('controller.selected'), - uploadButtonText: function() { - return this.get("uploading") ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture"); - }.property("uploading") - });