mirror of
https://github.com/discourse/discourse.git
synced 2024-12-01 07:03:43 +08:00
Merge pull request #1347 from ZogStriP/change-your-avatar-in-a-modal
change your avatar in a modal
This commit is contained in:
commit
b6e66372d4
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
The modal for selecting an avatar
|
||||
|
||||
@class AvatarSelectorController
|
||||
@extends Discourse.Controller
|
||||
@namespace Discourse
|
||||
@uses Discourse.ModalFunctionality
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AvatarSelectorController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
|
||||
init: function() {
|
||||
// copy some data to support the cancel action
|
||||
this.setProperties(this.get("currentUser").getProperties(
|
||||
"username",
|
||||
"has_uploaded_avatar",
|
||||
"use_uploaded_avatar",
|
||||
"gravatar_template",
|
||||
"uploaded_avatar_template"
|
||||
));
|
||||
},
|
||||
|
||||
toggleUseUploadedAvatar: function(toggle) {
|
||||
this.set("use_uploaded_avatar", toggle);
|
||||
},
|
||||
|
||||
saveAvatarSelection: function() {
|
||||
// sends the information to the server if it has changed
|
||||
if (this.get("use_uploaded_avatar") !== this.get("currentUser.use_uploaded_avatar")) {
|
||||
var data = { use_uploaded_avatar: this.get("use_uploaded_avatar") };
|
||||
Discourse.ajax("/users/" + this.get("currentUser.username") + "/preferences/avatar/toggle", { type: 'PUT', data: data });
|
||||
}
|
||||
// saves the data back to the currentUser object
|
||||
var currentUser = this.get("currentUser");
|
||||
currentUser.setProperties(this.getProperties(
|
||||
"has_uploaded_avatar",
|
||||
"use_uploaded_avatar",
|
||||
"gravatar_template",
|
||||
"uploaded_avatar_template"
|
||||
));
|
||||
if (this.get("use_uploaded_avatar")) {
|
||||
currentUser.set("avatar_template", this.get("uploaded_avatar_template"));
|
||||
} else {
|
||||
currentUser.set("avatar_template", this.get("gravatar_template"));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,84 +0,0 @@
|
|||
/**
|
||||
This controller supports actions related to updating one's avatar
|
||||
|
||||
@class PreferencesAvatarController
|
||||
@extends Discourse.ObjectController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.PreferencesAvatarController = Discourse.ObjectController.extend({
|
||||
uploading: false,
|
||||
uploadProgress: 0,
|
||||
uploadDisabled: Em.computed.or("uploading"),
|
||||
useGravatar: Em.computed.not("use_uploaded_avatar"),
|
||||
useUploadedAvatar: Em.computed.alias("use_uploaded_avatar"),
|
||||
|
||||
toggleUseUploadedAvatar: function(toggle) {
|
||||
if (this.get("use_uploaded_avatar") !== toggle) {
|
||||
var controller = this;
|
||||
this.set("use_uploaded_avatar", toggle);
|
||||
Discourse.ajax("/users/" + this.get("username") + "/preferences/avatar/toggle", { type: 'PUT', data: { use_uploaded_avatar: toggle }})
|
||||
.then(function(result) { controller.set("avatar_template", result.avatar_template); });
|
||||
}
|
||||
},
|
||||
|
||||
uploadButtonText: function() {
|
||||
return this.get("uploading") ? I18n.t("user.change_avatar.uploading") : I18n.t("user.change_avatar.upload");
|
||||
}.property("uploading"),
|
||||
|
||||
uploadAvatar: function() {
|
||||
var controller = this;
|
||||
var $upload = $("#avatar-input");
|
||||
|
||||
// do nothing if no file is selected
|
||||
if (Em.isEmpty($upload.val())) { return; }
|
||||
|
||||
this.set("uploading", true);
|
||||
|
||||
// define the upload endpoint
|
||||
$upload.fileupload({
|
||||
url: Discourse.getURL("/users/" + this.get("username") + "/preferences/avatar"),
|
||||
dataType: "json",
|
||||
timeout: 20000
|
||||
});
|
||||
|
||||
// when there is a progression for the upload
|
||||
$upload.on("fileuploadprogressall", function (e, data) {
|
||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
controller.set("uploadProgress", progress);
|
||||
});
|
||||
|
||||
// when the upload is successful
|
||||
$upload.on("fileuploaddone", function (e, data) {
|
||||
// set some properties
|
||||
controller.setProperties({
|
||||
has_uploaded_avatar: true,
|
||||
use_uploaded_avatar: true,
|
||||
avatar_template: data.result.url,
|
||||
uploaded_avatar_template: data.result.url
|
||||
});
|
||||
});
|
||||
|
||||
// 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 (e, data) {
|
||||
// prevent automatic upload when selecting a file
|
||||
$upload.fileupload("destroy");
|
||||
$upload.off();
|
||||
// clear file input
|
||||
$upload.val("");
|
||||
// indicate upload is done
|
||||
controller.setProperties({
|
||||
uploading: false,
|
||||
uploadProgress: 0
|
||||
});
|
||||
});
|
||||
|
||||
// *actually* launch the upload
|
||||
$("#avatar-input").fileupload("add", { fileInput: $("#avatar-input") });
|
||||
}
|
||||
});
|
|
@ -171,24 +171,9 @@ Handlebars.registerHelper('avatar', function(user, options) {
|
|||
Ember.Handlebars.registerBoundHelper('boundAvatar', function(user, options) {
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
size: options.hash.imageSize,
|
||||
avatarTemplate: Em.get(user, 'avatar_template')
|
||||
avatarTemplate: Em.get(user, options.hash.template || 'avatar_template')
|
||||
}));
|
||||
}, 'avatar_template');
|
||||
|
||||
/**
|
||||
Bound avatar helper.
|
||||
Will rerender whenever the "uploaded_avatar_template" changes.
|
||||
Only available for the current user.
|
||||
|
||||
@method boundUploadedAvatar
|
||||
@for Handlebars
|
||||
**/
|
||||
Ember.Handlebars.registerBoundHelper('boundUploadedAvatar', function(user, options) {
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
size: options.hash.imageSize,
|
||||
avatarTemplate: Em.get(user, 'uploaded_avatar_template')
|
||||
}));
|
||||
}, 'uploaded_avatar_template');
|
||||
}, 'avatar_template', 'uploaded_avatar_template', 'gravatar_template');
|
||||
|
||||
/**
|
||||
Nicely format a date without a binding since the date doesn't need to change.
|
||||
|
|
|
@ -13,6 +13,13 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
|
|||
|
||||
renderTemplate: function() {
|
||||
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
|
||||
},
|
||||
|
||||
events: {
|
||||
showAvatarSelector: function() {
|
||||
Discourse.Route.showModal(this, 'avatarSelector');
|
||||
this.controllerFor("avatarSelector").init();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -117,32 +124,3 @@ Discourse.PreferencesUsernameRoute = Discourse.RestrictedUserRoute.extend({
|
|||
controller.setProperties({ model: user, newUsername: user.get('username') });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
The route for updating a user's avatar
|
||||
|
||||
@class PreferencesAvatarRoute
|
||||
@extends Discourse.RestrictedUserRoute
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.PreferencesAvatarRoute = Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
return this.render({ into: 'user', outlet: 'userOutlet' });
|
||||
},
|
||||
|
||||
// A bit odd, but if we leave to /preferences we need to re-render that outlet
|
||||
exit: function() {
|
||||
this._super();
|
||||
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
|
||||
},
|
||||
|
||||
setupController: function(controller, user) {
|
||||
controller.setProperties({ model: user });
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class='btn btn-primary' {{action saveAutoClose}} data-dismiss="modal">{{i18n topic.auto_close_save}}</button>
|
||||
<button class='btn' data-dismiss="modal">{{i18n topic.auto_close_cancel}}</button>
|
||||
<a data-dismiss="modal">{{i18n topic.auto_close_cancel}}</a>
|
||||
<button class='btn pull-right' {{action removeAutoClose}} data-dismiss="modal">{{i18n topic.auto_close_remove}}</button>
|
||||
</div>
|
|
@ -0,0 +1,29 @@
|
|||
<div class="modal-body">
|
||||
<div>
|
||||
<input type="radio" id="avatar" name="avatar" value="gravatar" {{action toggleUseUploadedAvatar false}}>
|
||||
<label class="radio" for="avatar">{{avatar controller imageSize="large" template="gravatar_template"}} {{{i18n user.change_avatar.gravatar}}} {{currentUser.email}}</label>
|
||||
<a href="//gravatar.com/emails" target="_blank" title="{{i18n user.change_avatar.gravatar_title}}" class="btn"><i class="icon-pencil"></i></a>
|
||||
<div>
|
||||
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded_avatar" {{action toggleUseUploadedAvatar true}} {{bindAttr disabled="view.uploadedAvatarDisabled" }}>
|
||||
<label class="radio" for="uploaded_avatar">
|
||||
{{#if has_uploaded_avatar}}
|
||||
{{boundAvatar controller imageSize="large" template="uploaded_avatar_template"}} {{i18n user.change_avatar.uploaded_avatar}}
|
||||
{{else}}
|
||||
{{i18n user.change_avatar.uploaded_avatar_empty}}
|
||||
{{/if}}
|
||||
</label>
|
||||
<button id="fake-avatar-input" class="btn" {{bindAttr disabled="view.uploading"}} title="{{i18n user.change_avatar.upload_title}}">
|
||||
<i class="icon-picture"></i> {{view.uploadButtonText}}
|
||||
</button>
|
||||
<input type="file" id="avatar-input" accept="image/*" style="display:none">
|
||||
{{#if view.uploading}}
|
||||
<span>{{i18n upload_selector.uploading}} {{view.uploadProgress}}%</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" {{action saveAvatarSelection}} data-dismiss="modal">{{i18n save}}</button>
|
||||
<a data-dismiss="modal">{{i18n cancel}}</a>
|
||||
</div>
|
|
@ -1,39 +0,0 @@
|
|||
<form class="form-horizontal">
|
||||
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<h3>{{i18n user.change_avatar.title}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{i18n user.avatar.title}}</label>
|
||||
<div class="controls">
|
||||
<label class="radio">
|
||||
<input type="radio" name="avatar" value="gravatar" {{action toggleUseUploadedAvatar false}}> {{avatar this imageSize="large" template="gravatar_template"}} {{i18n user.change_avatar.gravatar}} <a href="//gravatar.com/emails/" target="_blank" class="btn pad-left" title="{{i18n user.change_avatar.gravatar_title}}">{{i18n user.change}}</a>
|
||||
</label>
|
||||
{{#if has_uploaded_avatar}}
|
||||
<label class="radio">
|
||||
<input type="radio" name="avatar" value="uploaded_avatar" {{action toggleUseUploadedAvatar true}}> {{boundUploadedAvatar this imageSize="large"}} {{i18n user.change_avatar.uploaded_avatar}}
|
||||
</label>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<div class="instructions">{{i18n user.change_avatar.upload_instructions}}</div>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<input type="file" id="avatar-input" accept="image/*">
|
||||
</div>
|
||||
<button {{action uploadAvatar}} {{bindAttr disabled="uploadDisabled"}} class="btn btn-primary">
|
||||
<span class="add-upload"><i class="icon-picture"></i><i class="icon-plus"></i></span>
|
||||
{{uploadButtonText}}
|
||||
</button>
|
||||
{{#if uploading}}
|
||||
<span>{{i18n upload_selector.uploading}} {{uploadProgress}}%</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
|
@ -44,7 +44,8 @@
|
|||
<div class="control-group">
|
||||
<label class="control-label">{{i18n user.avatar.title}}</label>
|
||||
<div class="controls">
|
||||
{{avatar model imageSize="large"}}
|
||||
{{boundAvatar model imageSize="large"}}
|
||||
<button {{action showAvatarSelector}} class="btn pad-left">{{i18n user.change}}</button>
|
||||
</div>
|
||||
<div class='instructions'>
|
||||
{{#if Discourse.SiteSettings.allow_uploaded_avatars}}
|
||||
|
@ -53,7 +54,6 @@
|
|||
{{else}}
|
||||
{{{i18n user.avatar.instructions.gravatar}}} {{email}}
|
||||
{{/if}}
|
||||
{{#linkTo "preferences.avatar" class="btn pad-left"}}{{i18n user.change}}{{/linkTo}}
|
||||
{{else}}
|
||||
{{{i18n user.avatar.instructions.gravatar}}} {{email}}
|
||||
<a href="//gravatar.com/emails/" target="_blank" title="{{i18n user.change_avatar.gravatar_title}}" class="btn pad-left">{{i18n user.change}}</a>
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
This view handles the avatar selection interface
|
||||
|
||||
@class AvatarSelectorView
|
||||
@extends Discourse.ModalBodyView
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
||||
templateName: 'modal/avatar_selector',
|
||||
classNames: ['avatar-selector'],
|
||||
title: I18n.t('user.change_avatar.title'),
|
||||
uploading: false,
|
||||
uploadProgress: 0,
|
||||
uploadedAvatarDisabled: Em.computed.not("controller.has_uploaded_avatar"),
|
||||
|
||||
didInsertElement: function() {
|
||||
var view = 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/avatar"),
|
||||
dataType: "json",
|
||||
timeout: 20000,
|
||||
fileInput: $upload
|
||||
});
|
||||
|
||||
// when a file has been selected
|
||||
$upload.on("fileuploadadd", function (e, data) {
|
||||
view.set("uploading", true);
|
||||
});
|
||||
|
||||
// when there is a progression for the upload
|
||||
$upload.on("fileuploadprogressall", function (e, data) {
|
||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
view.set("uploadProgress", progress);
|
||||
});
|
||||
|
||||
// when the upload is successful
|
||||
$upload.on("fileuploaddone", function (e, data) {
|
||||
// set some properties
|
||||
view.get("controller").setProperties({
|
||||
has_uploaded_avatar: true,
|
||||
use_uploaded_avatar: true,
|
||||
uploaded_avatar_template: data.result.url
|
||||
});
|
||||
});
|
||||
|
||||
// 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 (e, data) {
|
||||
view.setProperties({ uploading: false, uploadProgress: 0 });
|
||||
});
|
||||
},
|
||||
|
||||
willDestroyElement: function() {
|
||||
$("#fake-avatar-input").off("click");
|
||||
$("#avatar-input").fileupload("destroy");
|
||||
},
|
||||
|
||||
// *HACK* used to select the proper radio button
|
||||
selectedChanged: function() {
|
||||
var view = this;
|
||||
Em.run.next(function() {
|
||||
var value = view.get('controller.use_uploaded_avatar') ? 'uploaded_avatar' : 'gravatar';
|
||||
view.$('input:radio[name="avatar"]').val([value]);
|
||||
});
|
||||
}.observes('controller.use_uploaded_avatar'),
|
||||
|
||||
uploadButtonText: function() {
|
||||
return this.get("uploading") ? I18n.t("uploading") : I18n.t("upload");
|
||||
}.property("uploading")
|
||||
|
||||
});
|
|
@ -1,21 +0,0 @@
|
|||
/**
|
||||
This view handles rendering of a user's avatar uploader
|
||||
|
||||
@class PreferencesAvatarView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.PreferencesAvatarView = Discourse.View.extend({
|
||||
templateName: "user/avatar",
|
||||
classNames: ["user-preferences"],
|
||||
|
||||
selectedChanged: function() {
|
||||
var view = this;
|
||||
Em.run.next(function() {
|
||||
var value = view.get("controller.use_uploaded_avatar") ? "uploaded_avatar" : "gravatar";
|
||||
view.$('input:radio[name="avatar"]').val([value]);
|
||||
});
|
||||
}.observes('controller.use_uploaded_avatar')
|
||||
|
||||
});
|
|
@ -323,3 +323,18 @@
|
|||
width: 680px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-selector {
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
#avatar-input {
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avatar {
|
||||
margin: 5px 10px 5px 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -376,7 +376,7 @@ class UsersController < ApplicationController
|
|||
user.use_uploaded_avatar = params[:use_uploaded_avatar]
|
||||
user.save!
|
||||
|
||||
render json: { avatar_template: user.avatar_template }
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -218,12 +218,11 @@ en:
|
|||
|
||||
change_avatar:
|
||||
title: "Change your avatar"
|
||||
upload_instructions: "Or you could upload an image"
|
||||
upload: "Upload a picture"
|
||||
uploading: "Uploading the picture..."
|
||||
gravatar: "Gravatar"
|
||||
gravatar: "<a href='//gravatar.com/emails' target='_blank'>Gravatar</a>, based on"
|
||||
gravatar_title: "Change your avatar on Gravatar's website"
|
||||
uploaded_avatar: "Uploaded picture"
|
||||
uploaded_avatar: "Custom picture"
|
||||
uploaded_avatar_empty: "Add a custom picture"
|
||||
upload_title: "Upload your picture"
|
||||
|
||||
email:
|
||||
title: "Email"
|
||||
|
|
Loading…
Reference in New Issue
Block a user