FIX: profile picture selector

This commit is contained in:
Régis Hanol 2015-09-11 12:56:34 +02:00
parent 6437cd0341
commit 0c58f08207
11 changed files with 116 additions and 117 deletions

View File

@ -1,3 +1,4 @@
import computed from "ember-addons/ember-computed-decorators";
import UploadMixin from "discourse/mixins/upload";
export default Em.Component.extend(UploadMixin, {
@ -5,21 +6,23 @@ export default Em.Component.extend(UploadMixin, {
tagName: "span",
imageIsNotASquare: false,
uploadButtonText: function() {
return this.get("uploading") ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture");
}.property("uploading"),
@computed("uploading")
uploadButtonText(uploading) {
return uploading ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture");
},
uploadDone(upload) {
this.setProperties({
imageIsNotASquare: upload.width !== upload.height,
uploadedAvatarTemplate: upload.url,
custom_avatar_upload_id: upload.id,
uploadedAvatarId: upload.id,
});
this.sendAction("done");
},
data: function() {
return { user_id: this.get("user_id") };
}.property("user_id")
@computed("user_id")
data(user_id) {
return { user_id };
}
});

View File

@ -1,21 +1,29 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import computed from "ember-addons/ember-computed-decorators";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Ember.Controller.extend(ModalFunctionality, {
uploadedAvatarTemplate: null,
saveDisabled: Em.computed.alias("uploading"),
hasUploadedAvatar: Em.computed.or('uploadedAvatarTemplate', 'custom_avatar_upload_id'),
selectedUploadId: function() {
switch (this.get("selected")) {
case "system": return this.get("system_avatar_upload_id");
case "gravatar": return this.get("gravatar_avatar_upload_id");
default: return this.get("custom_avatar_upload_id");
@computed("selected", "system_avatar_upload_id", "gravatar_avatar_upload_id", "custom_avatar_upload_id")
selectedUploadId(selected, system, gravatar, custom) {
switch (selected) {
case "system": return system;
case "gravatar": return gravatar;
default: return custom;
}
}.property('selected', 'system_avatar_upload_id', 'gravatar_avatar_upload_id', 'custom_avatar_upload_id'),
},
allowImageUpload: function() {
@computed("selected", "system_avatar_template", "gravatar_avatar_template", "custom_avatar_template")
selectedAvatarTemplate(selected, system, gravatar, custom) {
switch (selected) {
case "system": return system;
case "gravatar": return gravatar;
default: return custom;
}
},
@computed()
allowImageUpload() {
return Discourse.Utilities.allowsImages();
}.property(),
},
actions: {
useUploadedAvatar() { this.set("selected", "uploaded"); },
@ -25,8 +33,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
refreshGravatar() {
this.set("gravatarRefreshDisabled", true);
return Discourse
.ajax("/user_avatar/" + this.get("username") + "/refresh_gravatar.json", { method: 'POST' })
.then(result => this.set("gravatar_avatar_upload_id", result.upload_id))
.ajax(`/user_avatar/${this.get("username")}/refresh_gravatar.json`, { method: "POST" })
.then(result => this.setProperties({
gravatar_avatar_template: result.gravatar_avatar_template,
gravatar_upload_id: result.gravatar_upload_id,
}))
.finally(() => this.set("gravatarRefreshDisabled", false));
}
}

View File

@ -3,32 +3,27 @@ import { longDate, autoUpdatingRelativeAge, number } from 'discourse/lib/formatt
const safe = Handlebars.SafeString;
Em.Handlebars.helper('bound-avatar', function(user, size) {
Em.Handlebars.helper('bound-avatar', (user, size) => {
if (Em.isEmpty(user)) {
return new safe("<div class='avatar-placeholder'></div>");
}
const avatar = Em.get(user, 'avatar_template');
return new safe(Discourse.Utilities.avatarImg({ size: size, avatarTemplate: avatar }));
}, 'username', 'avatar_template');
/*
* Used when we only have a template
*/
Em.Handlebars.helper('bound-avatar-template', function(at, size) {
Em.Handlebars.helper('bound-avatar-template', (at, size) => {
return new safe(Discourse.Utilities.avatarImg({ size: size, avatarTemplate: at }));
});
registerUnbound('raw-date', function(dt) {
return longDate(new Date(dt));
});
registerUnbound('raw-date', dt => longDate(new Date(dt)));
registerUnbound('age-with-tooltip', function(dt) {
return new safe(autoUpdatingRelativeAge(new Date(dt), {title: true}));
});
registerUnbound('age-with-tooltip', dt => new safe(autoUpdatingRelativeAge(new Date(dt), {title: true})));
registerUnbound('number', function(orig, params) {
registerUnbound('number', (orig, params) => {
orig = parseInt(orig, 10);
if (isNaN(orig)) { orig = 0; }

View File

@ -256,47 +256,33 @@ const User = RestModel.extend({
});
},
/*
Change avatar selection
*/
pickAvatar(uploadId) {
pickAvatar(upload_id, avatar_template) {
return Discourse.ajax(`/users/${this.get("username_lower")}/preferences/avatar/pick`, {
type: 'PUT',
data: { upload_id: uploadId }
}).then(() => this.set('uploaded_avatar_id', uploadId));
data: { upload_id }
}).then(() => this.setProperties({
avatar_template,
uploaded_avatar_id: upload_id
}));
},
/**
Determines whether the current user is allowed to upload a file.
@method isAllowedToUploadAFile
@param {String} type The type of the upload (image, attachment)
@returns true if the current user is allowed to upload a file
**/
isAllowedToUploadAFile(type) {
return this.get('staff') ||
this.get('trust_level') > 0 ||
Discourse.SiteSettings['newuser_max_' + type + 's'] > 0;
},
/**
Invite a user to the site
@method createInvite
@param {String} email The email address of the user to invite to the site
@returns {Promise} the result of the server call
**/
createInvite(email, groupNames) {
createInvite(email, group_names) {
return Discourse.ajax('/invites', {
type: 'POST',
data: {email: email, group_names: groupNames}
data: { email, group_names }
});
},
generateInviteLink(email, groupNames, topicId) {
generateInviteLink(email, group_names, topic_id) {
return Discourse.ajax('/invites/link', {
type: 'POST',
data: {email: email, group_names: groupNames, topic_id: topicId}
data: { email, group_names, topic_id }
});
},

View File

@ -18,50 +18,51 @@ export default RestrictedUserRoute.extend({
showModal('avatar-selector');
// all the properties needed for displaying the avatar selector modal
const controller = this.controllerFor('avatar-selector'),
props = this.modelFor('user').getProperties(
const props = this.modelFor('user').getProperties(
'id',
'email',
'username',
'uploaded_avatar_id',
'avatar_template',
'system_avatar_template',
'gravatar_avatar_template',
'custom_avatar_template',
'system_avatar_upload_id',
'gravatar_avatar_upload_id',
'custom_avatar_upload_id'
);
switch (props.uploaded_avatar_id) {
case props.system_avatar_upload_id:
switch (props.avatar_template) {
case props.system_avatar_template:
props.selected = "system";
break;
case props.gravatar_avatar_upload_id:
case props.gravatar_avatar_template:
props.selected = "gravatar";
break;
default:
props.selected = "uploaded";
}
controller.setProperties(props);
this.controllerFor('avatar-selector').setProperties(props);
},
saveAvatarSelection() {
const user = this.modelFor('user'),
avatarSelector = this.controllerFor('avatar-selector');
controller = this.controllerFor('avatar-selector'),
selectedUploadId = controller.get("selectedUploadId"),
selectedAvatarTemplate = controller.get("selectedAvatarTemplate");
// sends the information to the server if it has changed
if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) {
user.pickAvatar(avatarSelector.get('selectedUploadId'))
.then(() => {
user.setProperties(avatarSelector.getProperties(
'system_avatar_upload_id',
'gravatar_avatar_upload_id',
'custom_avatar_upload_id'
));
bootbox.alert(I18n.t("user.change_avatar.cache_notice"));
});
}
user.pickAvatar(selectedUploadId, selectedAvatarTemplate)
.then(() => {
user.setProperties(controller.getProperties(
'system_avatar_template',
'gravatar_avatar_template',
'custom_avatar_template'
));
bootbox.alert(I18n.t("user.change_avatar.cache_notice"));
});
// saves the data back
avatarSelector.send('closeModal');
controller.send('closeModal');
},
}

View File

@ -2,32 +2,27 @@
<div>
<div>
<input type="radio" id="system-avatar" name="avatar" value="system" {{action "useSystem"}}>
<label class="radio" for="system-avatar">{{bound-avatar controller "large" system_avatar_upload_id}} {{{i18n 'user.change_avatar.letter_based'}}}</label>
<label class="radio" for="system-avatar">{{bound-avatar-template system_avatar_template "large"}} {{{i18n 'user.change_avatar.letter_based'}}}</label>
</div>
<div>
<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-template gravatar_avatar_template "large"}} {{{i18n 'user.change_avatar.gravatar'}}} {{email}}</label>
{{d-button action="refreshGravatar" title="user.change_avatar.refresh_gravatar_title" disabled=gravatarRefreshDisabled icon="refresh"}}
</div>
{{#if allowImageUpload}}
<div>
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded" {{action "useUploadedAvatar"}}>
<label class="radio" for="uploaded_avatar">
{{#if hasUploadedAvatar}}
{{#if uploadedAvatarTemplate}}
{{bound-avatar-template uploadedAvatarTemplate "large"}}
{{else}}
{{bound-avatar controller "large" custom_avatar_upload_id}}
{{/if}}
{{#if custom_avatar_template}}
{{bound-avatar-template custom_avatar_template "large"}}
{{i18n 'user.change_avatar.uploaded_avatar'}}
{{else}}
{{i18n 'user.change_avatar.uploaded_avatar_empty'}}
{{/if}}
</label>
{{avatar-uploader username=username
user_id=id
uploadedAvatarTemplate=uploadedAvatarTemplate
custom_avatar_upload_id=custom_avatar_upload_id
{{avatar-uploader user_id=id
uploadedAvatarTemplate=custom_avatar_template
uploadedAvatarId=custom_avatar_upload_id
uploading=uploading
done="useUploadedAvatar"}}
</div>
@ -36,6 +31,6 @@
</div>
<div class="modal-footer">
{{d-button action="saveAvatarSelection" class="btn-primary" disabled=saveDisabled label="save"}}
{{d-button action="saveAvatarSelection" class="btn-primary" disabled=uploading label="save"}}
<a {{action "closeModal"}}>{{i18n 'cancel'}}</a>
</div>

View File

@ -1,3 +1,4 @@
import { on, observes } from "ember-addons/ember-computed-decorators";
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
@ -6,11 +7,14 @@ export default ModalBodyView.extend({
title: I18n.t('user.change_avatar.title'),
// *HACK* used to select the proper radio button, because {{action}} stops the default behavior
selectedChanged: function() {
@on("didInsertElement")
@observes("controller.selected")
selectedChanged() {
Em.run.next(() => $('input:radio[name="avatar"]').val([this.get('controller.selected')]));
}.observes('controller.selected').on("didInsertElement"),
},
_focusSelectedButton: function() {
@on("didInsertElement")
_focusSelectedButton() {
Em.run.next(() => $('input:radio[value="' + this.get('controller.selected') + '"]').focus());
}.on("didInsertElement")
}
});

View File

@ -13,7 +13,10 @@ class UserAvatarsController < ApplicationController
user.create_user_avatar(user_id: user.id) unless user.user_avatar
user.user_avatar.update_gravatar!
render json: { upload_id: user.user_avatar.gravatar_upload_id }
render json: {
gravatar_upload_id: user.user_avatar.gravatar_upload_id,
gravatar_avatar_template: User.avatar_template(user.username, user.user_avatar.gravatar_upload_id)
}
else
raise Discourse::NotFound
end

View File

@ -471,9 +471,8 @@ class User < ActiveRecord::Base
def self.system_avatar_template(username)
# TODO it may be worth caching this in a distributed cache, should be benched
if SiteSetting.external_system_avatars_enabled
color = letter_avatar_color(username)
url = SiteSetting.external_system_avatars_url.dup
url.gsub! "{color}", color
url.gsub! "{color}", letter_avatar_color(username)
url.gsub! "{username}", username
url.gsub! "{first_letter}", username[0].downcase
url
@ -482,10 +481,6 @@ class User < ActiveRecord::Base
end
end
def letter_avatar_color
self.class.letter_avatar_color(username)
end
def self.letter_avatar_color(username)
username = username || ""
color = LetterAvatar::COLORS[Digest::MD5.hexdigest(username)[0...15].to_i(16) % LetterAvatar::COLORS.length]

View File

@ -93,8 +93,12 @@ class UserSerializer < BasicUserSerializer
:watched_category_ids,
:private_messages_stats,
:disable_jump_reply,
:system_avatar_upload_id,
:system_avatar_template,
:gravatar_avatar_upload_id,
:gravatar_avatar_template,
:custom_avatar_upload_id,
:custom_avatar_template,
:has_title_badges,
:card_image_badge,
:card_image_badge_id,
@ -278,14 +282,32 @@ class UserSerializer < BasicUserSerializer
UserAction.private_messages_stats(object.id, scope)
end
def system_avatar_upload_id
# should be left blank
end
def system_avatar_template
User.system_avatar_template(object.username)
end
def gravatar_avatar_upload_id
object.user_avatar.try(:gravatar_upload_id)
end
def gravatar_avatar_template
return unless gravatar_upload_id = object.user_avatar.try(:gravatar_upload_id)
User.avatar_template(object.username, gravatar_upload_id)
end
def custom_avatar_upload_id
object.user_avatar.try(:custom_upload_id)
end
def custom_avatar_template
return unless custom_upload_id = object.user_avatar.try(:custom_upload_id)
User.avatar_template(object.username, custom_upload_id)
end
def has_title_badges
object.badges.where(allow_title: true).count > 0
end

View File

@ -1,16 +0,0 @@
import avatarTemplate from 'discourse/lib/avatar-template';
module('lib:avatar-template');
test("avatarTemplate", function(){
var oldCDN = Discourse.CDN;
var oldBase = Discourse.BaseUrl;
Discourse.BaseUrl = "frogs.com";
equal(avatarTemplate("sam", 1), "/user_avatar/frogs.com/sam/{size}/1.png");
Discourse.CDN = "http://awesome.cdn.com";
equal(avatarTemplate("sam", 1), "http://awesome.cdn.com/user_avatar/frogs.com/sam/{size}/1.png");
Discourse.CDN = oldCDN;
Discourse.BaseUrl = oldBase;
});