Merge pull request #1372 from ZogStriP/site-setting-for-allowing-animated-avatars

add a site setting for allowing animated avatars
This commit is contained in:
Jeff Atwood 2013-08-23 14:26:33 -07:00
commit ee4ffe7abe
7 changed files with 79 additions and 6 deletions

View File

@ -291,6 +291,54 @@ Discourse.Utilities = {
}
// otherwise, display a generic error message
bootbox.alert(I18n.t('post.errors.upload'));
},
/**
Crop an image to be used as avatar.
Simulate the "centered square thumbnail" generation done server-side.
Uses only the first frame of animated gifs when they are disabled.
@method cropAvatar
@param {String} url The url of the avatar
@param {String} fileType The file type of the uploaded file
@returns {Ember.Deferred} a promise that will eventually be the cropped avatar.
**/
cropAvatar: function(url, fileType) {
if (Discourse.SiteSettings.allow_animated_avatars && fileType === "image/gif") {
// can't crop animated gifs... let the browser stretch the gif
return Ember.RSVP.resolve(url);
} else {
return Ember.Deferred.promise(function(promise) {
var image = document.createElement("img");
// this event will be fired as soon as the image is loaded
image.onload = function(e) {
var img = e.target;
// computes the dimension & position (x, y) of the largest square we can fit in the image
var width = img.width, height = img.height, dimension, center, x, y;
if (width <= height) {
dimension = width;
center = height / 2;
x = 0;
y = center - (dimension / 2);
} else {
dimension = height;
center = width / 2;
x = center - (dimension / 2);
y = 0;
}
// set the size of the canvas to the maximum available size for avatars (browser will take care of downsizing the image)
var canvas = document.createElement("canvas");
var size = Discourse.Utilities.getRawSize(Discourse.Utilities.translateSize("huge"));
canvas.height = canvas.width = size;
// draw the image into the canvas
canvas.getContext("2d").drawImage(img, x, y, dimension, dimension, 0, 0, size, size);
// retrieve the image from the canvas
promise.resolve(canvas.toDataURL(fileType));
};
// launch the onload event
image.src = url;
});
}
}
};

View File

@ -51,11 +51,17 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
// when the upload is successful
$upload.on("fileuploaddone", function (e, data) {
// set some properties
// indicates the users is using an uploaded avatar
view.get("controller").setProperties({
has_uploaded_avatar: true,
use_uploaded_avatar: true,
uploaded_avatar_template: data.result.url
use_uploaded_avatar: true
});
// 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) {
view.get("controller").set("uploaded_avatar_template", avatarTemplate);
});
});

View File

@ -19,7 +19,7 @@ class OptimizedImage < ActiveRecord::Base
temp_file = Tempfile.new(["discourse-thumbnail", File.extname(original_path)])
temp_path = temp_file.path
if ImageSorcery.new(original_path).convert(temp_path, resize: "#{width}x#{height}")
if ImageSorcery.new("#{original_path}[0]").convert(temp_path, resize: "#{width}x#{height}")
thumbnail = OptimizedImage.create!(
upload_id: upload.id,
sha1: Digest::SHA1.file(temp_path).hexdigest,

View File

@ -245,6 +245,7 @@ class SiteSetting < ActiveRecord::Base
setting(:username_change_period, 3) # days
client_setting(:allow_uploaded_avatars, true)
client_setting(:allow_animated_avatars, false)
def self.generate_api_key!
self.api_key = SecureRandom.hex(32)

View File

@ -665,7 +665,8 @@ en:
delete_all_posts_max: "The maximum number of posts that can be deleted at once with the Delete All Posts button. If a user has more than this many posts, the posts cannot all be deleted at once and the user can't be deleted."
username_change_period: "The number of days after registration that accounts can change their username."
allow_uploaded_avatars: "Allow support for uploaded avatars"
allow_uploaded_avatars: "Allow users to upload their custom avatars"
allow_animated_avatars: "Allow users to use animated gif for avatars"
notification_types:
mentioned: "%{display_username} mentioned you in %{link}"

View File

@ -15,6 +15,10 @@ module Jobs
Discourse.store.path_for(upload)
end
# we'll extract the first frame when it's a gif
source = original_path
source << "[0]" unless SiteSetting.allow_animated_avatars
[120, 45, 32, 25, 20].each do |s|
# handle retina too
[s, s * 2].each do |size|
@ -22,7 +26,7 @@ module Jobs
temp_file = Tempfile.new(["discourse-avatar", File.extname(original_path)])
temp_path = temp_file.path
# create a centered square thumbnail
if ImageSorcery.new(original_path).convert(temp_path, gravity: "center", thumbnail: "#{size}x#{size}^", extent: "#{size}x#{size}", background: "transparent")
if ImageSorcery.new(source).convert(temp_path, gravity: "center", thumbnail: "#{size}x#{size}^", extent: "#{size}x#{size}", background: "transparent")
Discourse.store.store_avatar(temp_file, upload, size)
end
# close && remove temp file

View File

@ -132,3 +132,16 @@ test("avatarImg", function() {
blank(Discourse.Utilities.avatarImg({avatarTemplate: "", size: 'tiny'}),
"it doesn't render avatars for invalid avatar template");
});
module("Discourse.Utilities.cropAvatar with animated avatars", {
setup: function() { Discourse.SiteSettings.allow_animated_avatars = true; }
});
asyncTestDiscourse("cropAvatar", function() {
expect(1);
Discourse.Utilities.cropAvatar("/path/to/avatar.gif", "image/gif").then(function(avatarTemplate) {
equal(avatarTemplate, "/path/to/avatar.gif", "returns the url to the gif when animated gif are enabled");
start();
});
});