REFACTOR: uploading avatar should share code with upload component
This commit is contained in:
parent
4088fba4f2
commit
3cbb32cc20
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
export default Em.Component.extend({
|
import UploadMixin from 'discourse/mixins/upload';
|
||||||
uploading: false,
|
|
||||||
uploadProgress: 0,
|
export default Em.Component.extend(UploadMixin, {
|
||||||
|
|
||||||
backgroundStyle: function() {
|
backgroundStyle: function() {
|
||||||
var imageUrl = this.get('imageUrl');
|
var imageUrl = this.get('imageUrl');
|
||||||
|
@ -9,50 +9,11 @@ export default Em.Component.extend({
|
||||||
return "background-image: url(" + imageUrl + ")";
|
return "background-image: url(" + imageUrl + ")";
|
||||||
}.property('imageUrl'),
|
}.property('imageUrl'),
|
||||||
|
|
||||||
_initializeUploader: function() {
|
uploadDone: function(data) {
|
||||||
var $upload = this.$('input[type=file]'), // note: we can't cache this as fileupload replaces the input after upload
|
this.set('imageUrl', data.result.url);
|
||||||
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'),
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
selectFile: function() {
|
|
||||||
this.$('input[type=file]').click();
|
|
||||||
},
|
|
||||||
|
|
||||||
trash: function() {
|
trash: function() {
|
||||||
this.set('imageUrl', null);
|
this.set('imageUrl', null);
|
||||||
this.sendAction('clear');
|
this.sendAction('clear');
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -350,7 +350,6 @@ Discourse.User = Discourse.Model.extend({
|
||||||
@returns {Promise} the result of the clear profile background request
|
@returns {Promise} the result of the clear profile background request
|
||||||
*/
|
*/
|
||||||
clearProfileBackground: function() {
|
clearProfileBackground: function() {
|
||||||
var user = this;
|
|
||||||
return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/profile_background/clear", {
|
return Discourse.ajax("/users/" + this.get("username_lower") + "/preferences/profile_background/clear", {
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
data: { }
|
data: { }
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<input type="file" accept="image/*" style="display:none" />
|
||||||
|
<button class="btn" {{action selectFile}} {{bind-attr disabled="uploading"}} title="{{i18n user.change_avatar.upload_title}}">
|
||||||
|
<i class="fa fa-picture-o"></i> {{uploadButtonText}}
|
||||||
|
</button>
|
||||||
|
{{#if uploading}}
|
||||||
|
<span>{{i18n upload_selector.uploading}} {{view.uploadProgress}}%</span>
|
||||||
|
{{/if}}
|
||||||
|
{{#if imageIsNotASquare}}
|
||||||
|
<div class="warning">{{i18n user.change_avatar.image_is_not_a_square}}</div>
|
||||||
|
{{/if}}
|
|
@ -7,7 +7,7 @@
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="gravatar" name="avatar" value="gravatar" {{action useGravatar}}>
|
<input type="radio" id="gravatar" name="avatar" value="gravatar" {{action useGravatar}}>
|
||||||
<label class="radio" for="gravatar">{{bound-avatar controller "large" gravatar_avatar_upload_id}} {{{i18n user.change_avatar.gravatar}}} {{email}}</label>
|
<label class="radio" for="gravatar">{{bound-avatar controller "large" gravatar_avatar_upload_id}} {{{i18n user.change_avatar.gravatar}}} {{email}}</label>
|
||||||
<button href="#" {{action refreshGravatar}} title="{{i18n user.change_avatar.refresh_gravatar_title}}" {{bind-attr enabled="view.gravatarRefreshEnabled"}} class="btn no-text"><i class="fa fa-refresh"></i></button>
|
<button href {{action refreshGravatar}} title="{{i18n user.change_avatar.refresh_gravatar_title}}" {{bind-attr enabled="view.gravatarRefreshEnabled"}} class="btn no-text"><i class="fa fa-refresh"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded" {{action useUploadedAvatar}}>
|
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded" {{action useUploadedAvatar}}>
|
||||||
|
@ -22,16 +22,9 @@
|
||||||
{{i18n user.change_avatar.uploaded_avatar_empty}}
|
{{i18n user.change_avatar.uploaded_avatar_empty}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</label>
|
</label>
|
||||||
<button id="fake-avatar-input" class="btn" {{bind-attr disabled="view.uploading"}} title="{{i18n user.change_avatar.upload_title}}">
|
{{avatar-uploader username=username
|
||||||
<i class="fa fa-picture-o"></i> {{view.uploadButtonText}}
|
uploadedAvatarTemplate=view.uploadedAvatarTemplate
|
||||||
</button>
|
custom_avatar_upload_id=controller.custom_avatar_upload_id}}
|
||||||
<input type="file" id="avatar-input" accept="image/*" style="display:none">
|
|
||||||
{{#if view.uploading}}
|
|
||||||
<span>{{i18n upload_selector.uploading}} {{view.uploadProgress}}%</span>
|
|
||||||
{{/if}}
|
|
||||||
{{#if view.imageIsNotASquare}}
|
|
||||||
<div class="warning">{{i18n user.change_avatar.image_is_not_a_square}}</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,90 +10,10 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
||||||
templateName: 'modal/avatar_selector',
|
templateName: 'modal/avatar_selector',
|
||||||
classNames: ['avatar-selector'],
|
classNames: ['avatar-selector'],
|
||||||
title: I18n.t('user.change_avatar.title'),
|
title: I18n.t('user.change_avatar.title'),
|
||||||
uploading: false,
|
|
||||||
uploadProgress: 0,
|
|
||||||
saveDisabled: false,
|
saveDisabled: false,
|
||||||
gravatarRefreshEnabled: Em.computed.not('controller.gravatarRefreshDisabled'),
|
gravatarRefreshEnabled: Em.computed.not('controller.gravatarRefreshDisabled'),
|
||||||
imageIsNotASquare : false,
|
|
||||||
|
|
||||||
hasUploadedAvatar: Em.computed.or('uploadedAvatarTemplate', 'controller.custom_avatar_upload_id'),
|
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}}
|
// *HACK* used to select the proper radio button, cause {{action}}
|
||||||
// stops the default behavior
|
// stops the default behavior
|
||||||
selectedChanged: function() {
|
selectedChanged: function() {
|
||||||
|
@ -104,8 +24,4 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
||||||
});
|
});
|
||||||
}.observes('controller.selected'),
|
}.observes('controller.selected'),
|
||||||
|
|
||||||
uploadButtonText: function() {
|
|
||||||
return this.get("uploading") ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture");
|
|
||||||
}.property("uploading")
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue