FIX: profile picture wasn't properly updating

This commit is contained in:
Régis Hanol 2015-03-19 19:57:07 +01:00
parent 5084e2bdf1
commit 11bf7da63c
13 changed files with 82 additions and 80 deletions

View File

@ -455,7 +455,7 @@
{{#unless anonymizeForbidden}} {{#unless anonymizeForbidden}}
{{d-button label="admin.user.anonymize" {{d-button label="admin.user.anonymize"
icon="exclamation-triangle" icon="exclamation-triangle"
class="btn btn-danger" class="btn-danger"
disabled=anonymizeForbidden disabled=anonymizeForbidden
action="anonymize"}} action="anonymize"}}
{{/unless}} {{/unless}}
@ -463,7 +463,7 @@
{{#unless deleteForbidden}} {{#unless deleteForbidden}}
{{d-button label="admin.user.delete" {{d-button label="admin.user.delete"
icon="exclamation-triangle" icon="exclamation-triangle"
class="btn btn-danger" class="btn-danger"
disabled=deleteForbidden disabled=deleteForbidden
action="destroy"}} action="destroy"}}
{{/unless}} {{/unless}}

View File

@ -1,19 +1,19 @@
import UploadMixin from 'discourse/mixins/upload'; import UploadMixin from 'discourse/mixins/upload';
export default Em.Component.extend(UploadMixin, { export default Em.Component.extend(UploadMixin, {
type: 'avatar',
tagName: 'span', tagName: 'span',
imageIsNotASquare: false, imageIsNotASquare: false,
type: 'avatar',
uploadUrl: Discourse.computed.url('username', '/users/%@/preferences/user_image'), uploadUrl: Discourse.computed.url('username', '/users/%@/preferences/user_image'),
uploadButtonText: function() { uploadButtonText: function() {
return this.get("uploading") ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture"); return this.get("uploading") ?
I18n.t("uploading") :
I18n.t("user.change_avatar.upload_picture");
}.property("uploading"), }.property("uploading"),
uploadDone: function(data) { uploadDone(data) {
var self = this;
// display a warning whenever the image is not a square // display a warning whenever the image is not a square
this.set("imageIsNotASquare", data.result.width !== data.result.height); this.set("imageIsNotASquare", data.result.width !== data.result.height);
@ -21,13 +21,13 @@ export default Em.Component.extend(UploadMixin, {
// indeed, the server gives us back the url to the file we've just uploaded // 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 // 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 // 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) { Discourse.Utilities.cropAvatar(data.result.url, data.files[0].type).then(avatarTemplate => {
self.set("uploadedAvatarTemplate", avatarTemplate); this.set("uploadedAvatarTemplate", avatarTemplate);
// indicates the users is using an uploaded avatar (must happen after cropping, otherwise // indicates the users is using an uploaded avatar (must happen after cropping, otherwise
// we will attempt to load an invalid avatar and cache a redirect to old one, uploadedAvatarTemplate // we will attempt to load an invalid avatar and cache a redirect to old one, uploadedAvatarTemplate
// trumps over custom avatar upload id) // trumps over custom avatar upload id)
self.set("custom_avatar_upload_id", data.result.upload_id); this.set("custom_avatar_upload_id", data.result.upload_id);
}); });
// the upload is now done // the upload is now done

View File

@ -32,5 +32,6 @@ export default Ember.Component.extend({
click() { click() {
this.sendAction("action", this.get("actionParam")); this.sendAction("action", this.get("actionParam"));
return false;
} }
}); });

View File

@ -2,8 +2,10 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller'; import DiscourseController from 'discourse/controllers/controller';
export default DiscourseController.extend(ModalFunctionality, { export default DiscourseController.extend(ModalFunctionality, {
uploadedAvatarTemplate: null,
hasUploadedAvatar: Em.computed.or('uploadedAvatarTemplate', 'custom_avatar_upload_id'),
selectedUploadId: function(){ selectedUploadId: function() {
switch (this.get("selected")) { switch (this.get("selected")) {
case "system": return this.get("system_avatar_upload_id"); case "system": return this.get("system_avatar_upload_id");
case "gravatar": return this.get("gravatar_avatar_upload_id"); case "gravatar": return this.get("gravatar_avatar_upload_id");
@ -12,18 +14,16 @@ export default DiscourseController.extend(ModalFunctionality, {
}.property('selected', 'system_avatar_upload_id', 'gravatar_avatar_upload_id', 'custom_avatar_upload_id'), }.property('selected', 'system_avatar_upload_id', 'gravatar_avatar_upload_id', 'custom_avatar_upload_id'),
actions: { actions: {
useUploadedAvatar: function() { this.set("selected", "uploaded"); }, useUploadedAvatar() { this.set("selected", "uploaded"); },
useGravatar: function() { this.set("selected", "gravatar"); }, useGravatar() { this.set("selected", "gravatar"); },
useSystem: function() { this.set("selected", "system"); }, useSystem() { this.set("selected", "system"); },
refreshGravatar: function() {
var self = this; refreshGravatar() {
self.set("gravatarRefreshDisabled", true); this.set("gravatarRefreshDisabled", true);
Discourse return Discourse
.ajax("/user_avatar/" + this.get("username") + "/refresh_gravatar", {method: 'POST'}) .ajax("/user_avatar/" + this.get("username") + "/refresh_gravatar.json", { method: 'POST' })
.then(function(result){ .then(result => this.set("gravatar_avatar_upload_id", result.upload_id))
self.set("gravatarRefreshDisabled", false); .finally(() => this.set("gravatarRefreshDisabled", false));
self.set("gravatar_avatar_upload_id", result.upload_id);
});
} }
} }

View File

@ -1,4 +1,4 @@
export default function showModal(name, model) { export default (name, model) => {
// We use the container here because modals are like singletons // We use the container here because modals are like singletons
// in Discourse. Only one can be shown with a particular state. // in Discourse. Only one can be shown with a particular state.
const route = Discourse.__container__.lookup('route:application'); const route = Discourse.__container__.lookup('route:application');
@ -12,5 +12,4 @@ export default function showModal(name, model) {
if (controller.onShow) { controller.onShow(); } if (controller.onShow) { controller.onShow(); }
controller.set('flashMessage', null); controller.set('flashMessage', null);
} }
return controller; };
}

View File

@ -1,14 +1,13 @@
export default Em.Mixin.create({ export default Em.Mixin.create({
actions: { actions: {
didTransition: function() { didTransition() {
var self = this; Em.run.schedule("afterRender", () => {
Em.run.schedule("afterRender", function() { this.controllerFor("application").set("showFooter", true);
self.controllerFor("application").set("showFooter", true);
}); });
return true; return true;
}, },
willTransition: function() { willTransition() {
this.controllerFor("application").set("showFooter", false); this.controllerFor("application").set("showFooter", false);
return true; return true;
} }

View File

@ -8,7 +8,10 @@ export default RestrictedUserRoute.extend(ShowFooter, {
}, },
setupController(controller, user) { setupController(controller, user) {
controller.setProperties({ model: user, newNameInput: user.get('name') }); controller.setProperties({
model: user,
newNameInput: user.get('name')
});
}, },
actions: { actions: {
@ -16,10 +19,10 @@ export default RestrictedUserRoute.extend(ShowFooter, {
showModal('avatar-selector'); showModal('avatar-selector');
// all the properties needed for displaying the avatar selector modal // all the properties needed for displaying the avatar selector modal
const controller = this.controllerFor('avatar-selector'); const controller = this.controllerFor('avatar-selector'),
const user = this.modelFor('user'); props = this.modelFor('user').getProperties(
const props = user.getProperties( 'email',
'username', 'email', 'username',
'uploaded_avatar_id', 'uploaded_avatar_id',
'system_avatar_upload_id', 'system_avatar_upload_id',
'gravatar_avatar_upload_id', 'gravatar_avatar_upload_id',
@ -40,14 +43,14 @@ export default RestrictedUserRoute.extend(ShowFooter, {
controller.setProperties(props); controller.setProperties(props);
}, },
saveAvatarSelection: function() { saveAvatarSelection() {
const user = this.modelFor('user'); const user = this.modelFor('user'),
const avatarSelector = this.controllerFor('avatar-selector'); avatarSelector = this.controllerFor('avatar-selector');
// sends the information to the server if it has changed // sends the information to the server if it has changed
if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) { if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) {
user.pickAvatar(avatarSelector.get('selectedUploadId')) user.pickAvatar(avatarSelector.get('selectedUploadId'))
.then(function(){ .then(() => {
user.setProperties(avatarSelector.getProperties( user.setProperties(avatarSelector.getProperties(
'system_avatar_upload_id', 'system_avatar_upload_id',
'gravatar_avatar_upload_id', 'gravatar_avatar_upload_id',

View File

@ -2,9 +2,8 @@
export default Discourse.Route.extend({ export default Discourse.Route.extend({
afterModel: function() { afterModel() {
var user = this.modelFor('user'); if (!this.modelFor('user').get('can_edit')) {
if (!user.get('can_edit')) {
this.replaceWith('userActivity'); this.replaceWith('userActivity');
} }
} }

View File

@ -7,14 +7,14 @@
<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> {{d-button action="refreshGravatar" title="user.change_avatar.refresh_gravatar_title" disabled=gravatarRefreshDisabled class="no-text" icon="refresh"}}
</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"}}>
<label class="radio" for="uploaded_avatar"> <label class="radio" for="uploaded_avatar">
{{#if view.hasUploadedAvatar}} {{#if hasUploadedAvatar}}
{{#if view.uploadedAvatarTemplate}} {{#if uploadedAvatarTemplate}}
{{bound-avatar-template view.uploadedAvatarTemplate "large"}} {{bound-avatar-template uploadedAvatarTemplate "large"}}
{{else}} {{else}}
{{bound-avatar controller "large" custom_avatar_upload_id}} {{i18n 'user.change_avatar.uploaded_avatar'}} {{bound-avatar controller "large" custom_avatar_upload_id}} {{i18n 'user.change_avatar.uploaded_avatar'}}
{{/if}} {{/if}}
@ -23,14 +23,14 @@
{{/if}} {{/if}}
</label> </label>
{{avatar-uploader username=username {{avatar-uploader username=username
uploadedAvatarTemplate=view.uploadedAvatarTemplate uploadedAvatarTemplate=uploadedAvatarTemplate
custom_avatar_upload_id=controller.custom_avatar_upload_id custom_avatar_upload_id=custom_avatar_upload_id
done="useUploadedAvatar"}} done="useUploadedAvatar"}}
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-primary" {{action "saveAvatarSelection"}} {{bind-attr disabled="view.saveDisabled"}}>{{i18n 'save'}}</button> {{d-button action="saveAvatarSelection" class="btn-primary" disabled=saveDisabled label="save"}}
<a {{action "closeModal"}}>{{i18n 'cancel'}}</a> <a {{action "closeModal"}}>{{i18n 'cancel'}}</a>
</div> </div>

View File

@ -54,7 +54,7 @@
<div class="controls"> <div class="controls">
<span class='static'>{{email}}</span> <span class='static'>{{email}}</span>
{{#if can_edit_email}} {{#if can_edit_email}}
{{#link-to "preferences.email" class="btn btn-small pad-left no-text"}}<i class="fa fa-pencil"></i>{{/link-to}} {{#link-to "preferences.email" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
{{/if}} {{/if}}
</div> </div>
<div class='instructions'> <div class='instructions'>
@ -62,7 +62,7 @@
</div> </div>
{{else}} {{else}}
<div class="controls"> <div class="controls">
<button class="btn" title="{{i18n 'admin.users.check_email.title'}}" {{action "checkEmail" this}}>{{fa-icon "envelope-o"}} {{i18n 'admin.users.check_email.text'}}</button> {{d-button action="checkEmail" actionParam=this title="admin.users.check_email.title" icon="envelope-o" label="admin.users.check_email.text"}}
</div> </div>
{{/if}} {{/if}}
</div> </div>
@ -72,7 +72,8 @@
<div class="control-group pref-password"> <div class="control-group pref-password">
<label class="control-label">{{i18n 'user.password.title'}}</label> <label class="control-label">{{i18n 'user.password.title'}}</label>
<div class="controls"> <div class="controls">
<a href="#" {{action "changePassword"}} class='btn'><i class="fa fa-envelope"></i> <a href="#" {{action "changePassword"}} class='btn'>
{{fa-icon "envelope"}}
{{#if no_password}} {{#if no_password}}
{{i18n 'user.change_password.set_password'}} {{i18n 'user.change_password.set_password'}}
{{else}} {{else}}
@ -87,9 +88,10 @@
<div class="control-group pref-avatar"> <div class="control-group pref-avatar">
<label class="control-label">{{i18n 'user.avatar.title'}}</label> <label class="control-label">{{i18n 'user.avatar.title'}}</label>
<div class="controls"> <div class="controls">
{{bound-avatar model "large"}} {{! we want the "huge" version even though we're downsizing it to "large" in CSS }}
{{bound-avatar model "huge"}}
{{#if allowAvatarUpload}} {{#if allowAvatarUpload}}
<button {{action "showAvatarSelector"}} class="btn pad-left no-text">{{fa-icon "pencil"}}</button> {{d-button action="showAvatarSelector" class="pad-left no-text" icon="pencil"}}
{{else}} {{else}}
{{#unless ssoOverridesAvatar}} {{#unless ssoOverridesAvatar}}
<a href="//gravatar.com/emails" target="_blank" title="{{i18n 'user.change_avatar.gravatar_title'}}" class="btn no-text">{{fa-icon "pencil"}}</a> <a href="//gravatar.com/emails" target="_blank" title="{{i18n 'user.change_avatar.gravatar_title'}}" class="btn no-text">{{fa-icon "pencil"}}</a>
@ -245,7 +247,7 @@
<div class="control-group delete-account"> <div class="control-group delete-account">
<hr/> <hr/>
<div class="controls"> <div class="controls">
<button {{action "delete"}} {{bind-attr disabled="deleteDisabled"}} class="btn btn-danger"><i class="fa fa-trash-o"></i> {{i18n 'user.delete_account'}}</button> {{d-button action="delete" disabled="deleteDisabled" class="btn-danger" icon="trash-o" label="user.delete_account"}}
</div> </div>
</div> </div>
{{/if}} {{/if}}

View File

@ -4,9 +4,6 @@ export default 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'),
saveDisabled: false,
gravatarRefreshEnabled: Em.computed.not('controller.gravatarRefreshDisabled'),
hasUploadedAvatar: Em.computed.or('uploadedAvatarTemplate', 'controller.custom_avatar_upload_id'),
// *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

View File

@ -170,6 +170,13 @@
border-bottom: 1px solid scale-color-diff(); border-bottom: 1px solid scale-color-diff();
} }
} }
.pref-avatar {
.avatar {
max-width: 45px;
max-height: 45px;
}
}
} }
.about { .about {

View File

@ -3,7 +3,8 @@ moduleFor("controller:avatar-selector", "controller:avatar-selector", {
}); });
test("avatarTemplate", function() { test("avatarTemplate", function() {
var avatarSelectorController = this.subject(); const avatarSelectorController = this.subject();
avatarSelectorController.setProperties({ avatarSelectorController.setProperties({
selected: "system", selected: "system",
system_avatar_upload_id:1, system_avatar_upload_id:1,
@ -11,17 +12,11 @@ test("avatarTemplate", function() {
custom_avatar_upload_id: 3 custom_avatar_upload_id: 3
}); });
equal(avatarSelectorController.get("selectedUploadId"), 1, equal(avatarSelectorController.get("selectedUploadId"), 1, "we are using system by default");
"we are using system by default");
avatarSelectorController.set('selected', 'gravatar'); avatarSelectorController.set('selected', 'gravatar');
equal(avatarSelectorController.get("selectedUploadId"), 2, "we are using gravatar when set");
equal(avatarSelectorController.get("selectedUploadId"), 2,
"we are using gravatar when set");
avatarSelectorController.set("selected", "custom"); avatarSelectorController.set("selected", "custom");
equal(avatarSelectorController.get("selectedUploadId"), 3, "we are using custom when set");
equal(avatarSelectorController.get("selectedUploadId"), 3,
"we are using custom when set");
}); });