From ea6e73076bf1b4a67e86ae30661b6f0d7ef2ab4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Sat, 17 Aug 2013 00:29:54 +0200 Subject: [PATCH] change your avatar in a modal --- .../controllers/avatar_selector_controller.js | 46 ++++++++++ .../preferences_avatar_controller.js | 84 ----------------- .../discourse/helpers/application_helpers.js | 19 +--- .../discourse/routes/preferences_routes.js | 36 ++------ .../templates/modal/auto_close.js.handlebars | 4 +- .../modal/avatar_selector.js.handlebars | 29 ++++++ .../templates/user/avatar.js.handlebars | 39 -------- .../templates/user/preferences.js.handlebars | 4 +- .../views/modal/avatar_selector_view.js | 89 +++++++++++++++++++ .../views/user/preferences_avatar_view.js | 21 ----- .../{upload.scss => upload.css.scss} | 0 .../stylesheets/application/user.css.scss | 15 ++++ app/controllers/users_controller.rb | 2 +- config/locales/client.en.yml | 9 +- 14 files changed, 197 insertions(+), 200 deletions(-) create mode 100644 app/assets/javascripts/discourse/controllers/avatar_selector_controller.js delete mode 100644 app/assets/javascripts/discourse/controllers/preferences_avatar_controller.js create mode 100644 app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars delete mode 100644 app/assets/javascripts/discourse/templates/user/avatar.js.handlebars create mode 100644 app/assets/javascripts/discourse/views/modal/avatar_selector_view.js delete mode 100644 app/assets/javascripts/discourse/views/user/preferences_avatar_view.js rename app/assets/stylesheets/application/{upload.scss => upload.css.scss} (100%) diff --git a/app/assets/javascripts/discourse/controllers/avatar_selector_controller.js b/app/assets/javascripts/discourse/controllers/avatar_selector_controller.js new file mode 100644 index 00000000000..d4aebbc9793 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/avatar_selector_controller.js @@ -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")); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/preferences_avatar_controller.js b/app/assets/javascripts/discourse/controllers/preferences_avatar_controller.js deleted file mode 100644 index 41180495972..00000000000 --- a/app/assets/javascripts/discourse/controllers/preferences_avatar_controller.js +++ /dev/null @@ -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") }); - } -}); diff --git a/app/assets/javascripts/discourse/helpers/application_helpers.js b/app/assets/javascripts/discourse/helpers/application_helpers.js index b941f29144b..34ee470db11 100644 --- a/app/assets/javascripts/discourse/helpers/application_helpers.js +++ b/app/assets/javascripts/discourse/helpers/application_helpers.js @@ -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. diff --git a/app/assets/javascripts/discourse/routes/preferences_routes.js b/app/assets/javascripts/discourse/routes/preferences_routes.js index 55427f4b9ff..0ddc31a4579 100644 --- a/app/assets/javascripts/discourse/routes/preferences_routes.js +++ b/app/assets/javascripts/discourse/routes/preferences_routes.js @@ -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 }); - } -}); diff --git a/app/assets/javascripts/discourse/templates/modal/auto_close.js.handlebars b/app/assets/javascripts/discourse/templates/modal/auto_close.js.handlebars index 49d1d824e7d..7b400d1c8fd 100644 --- a/app/assets/javascripts/discourse/templates/modal/auto_close.js.handlebars +++ b/app/assets/javascripts/discourse/templates/modal/auto_close.js.handlebars @@ -5,6 +5,6 @@ \ No newline at end of file + diff --git a/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars b/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars new file mode 100644 index 00000000000..46e2be87b9d --- /dev/null +++ b/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars @@ -0,0 +1,29 @@ + + + diff --git a/app/assets/javascripts/discourse/templates/user/avatar.js.handlebars b/app/assets/javascripts/discourse/templates/user/avatar.js.handlebars deleted file mode 100644 index e72fb175e27..00000000000 --- a/app/assets/javascripts/discourse/templates/user/avatar.js.handlebars +++ /dev/null @@ -1,39 +0,0 @@ -
- -
-
-

{{i18n user.change_avatar.title}}

-
-
- -
- -
- - {{#if has_uploaded_avatar}} - - {{/if}} -
-
- -
-
{{i18n user.change_avatar.upload_instructions}}
-
-
- -
- - {{#if uploading}} - {{i18n upload_selector.uploading}} {{uploadProgress}}% - {{/if}} -
-
- -
diff --git a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars index 361525de797..a3faeecaa04 100644 --- a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars @@ -44,7 +44,8 @@
- {{avatar model imageSize="large"}} + {{boundAvatar model imageSize="large"}} +
{{#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}} {{i18n user.change}} diff --git a/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js b/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js new file mode 100644 index 00000000000..dde8c1c0df0 --- /dev/null +++ b/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js @@ -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") + +}); diff --git a/app/assets/javascripts/discourse/views/user/preferences_avatar_view.js b/app/assets/javascripts/discourse/views/user/preferences_avatar_view.js deleted file mode 100644 index 4eb5d6eff6c..00000000000 --- a/app/assets/javascripts/discourse/views/user/preferences_avatar_view.js +++ /dev/null @@ -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') - -}); diff --git a/app/assets/stylesheets/application/upload.scss b/app/assets/stylesheets/application/upload.css.scss similarity index 100% rename from app/assets/stylesheets/application/upload.scss rename to app/assets/stylesheets/application/upload.css.scss diff --git a/app/assets/stylesheets/application/user.css.scss b/app/assets/stylesheets/application/user.css.scss index a79ea581470..2fadb72b905 100644 --- a/app/assets/stylesheets/application/user.css.scss +++ b/app/assets/stylesheets/application/user.css.scss @@ -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; + } +} diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4137f24b311..681b4e9fef3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -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 diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 68a7acc2209..ccf8f58ea6b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -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: "Gravatar, 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"