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:
commit
ee4ffe7abe
|
@ -291,6 +291,54 @@ Discourse.Utilities = {
|
||||||
}
|
}
|
||||||
// otherwise, display a generic error message
|
// otherwise, display a generic error message
|
||||||
bootbox.alert(I18n.t('post.errors.upload'));
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,11 +51,17 @@ Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({
|
||||||
|
|
||||||
// when the upload is successful
|
// when the upload is successful
|
||||||
$upload.on("fileuploaddone", function (e, data) {
|
$upload.on("fileuploaddone", function (e, data) {
|
||||||
// set some properties
|
// indicates the users is using an uploaded avatar
|
||||||
view.get("controller").setProperties({
|
view.get("controller").setProperties({
|
||||||
has_uploaded_avatar: true,
|
has_uploaded_avatar: true,
|
||||||
use_uploaded_avatar: true,
|
use_uploaded_avatar: true
|
||||||
uploaded_avatar_template: data.result.url
|
});
|
||||||
|
// 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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ class OptimizedImage < ActiveRecord::Base
|
||||||
temp_file = Tempfile.new(["discourse-thumbnail", File.extname(original_path)])
|
temp_file = Tempfile.new(["discourse-thumbnail", File.extname(original_path)])
|
||||||
temp_path = temp_file.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!(
|
thumbnail = OptimizedImage.create!(
|
||||||
upload_id: upload.id,
|
upload_id: upload.id,
|
||||||
sha1: Digest::SHA1.file(temp_path).hexdigest,
|
sha1: Digest::SHA1.file(temp_path).hexdigest,
|
||||||
|
|
|
@ -245,6 +245,7 @@ class SiteSetting < ActiveRecord::Base
|
||||||
setting(:username_change_period, 3) # days
|
setting(:username_change_period, 3) # days
|
||||||
|
|
||||||
client_setting(:allow_uploaded_avatars, true)
|
client_setting(:allow_uploaded_avatars, true)
|
||||||
|
client_setting(:allow_animated_avatars, false)
|
||||||
|
|
||||||
def self.generate_api_key!
|
def self.generate_api_key!
|
||||||
self.api_key = SecureRandom.hex(32)
|
self.api_key = SecureRandom.hex(32)
|
||||||
|
|
|
@ -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."
|
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."
|
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:
|
notification_types:
|
||||||
mentioned: "%{display_username} mentioned you in %{link}"
|
mentioned: "%{display_username} mentioned you in %{link}"
|
||||||
|
|
|
@ -15,6 +15,10 @@ module Jobs
|
||||||
Discourse.store.path_for(upload)
|
Discourse.store.path_for(upload)
|
||||||
end
|
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|
|
[120, 45, 32, 25, 20].each do |s|
|
||||||
# handle retina too
|
# handle retina too
|
||||||
[s, s * 2].each do |size|
|
[s, s * 2].each do |size|
|
||||||
|
@ -22,7 +26,7 @@ module Jobs
|
||||||
temp_file = Tempfile.new(["discourse-avatar", File.extname(original_path)])
|
temp_file = Tempfile.new(["discourse-avatar", File.extname(original_path)])
|
||||||
temp_path = temp_file.path
|
temp_path = temp_file.path
|
||||||
# create a centered square thumbnail
|
# 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)
|
Discourse.store.store_avatar(temp_file, upload, size)
|
||||||
end
|
end
|
||||||
# close && remove temp file
|
# close && remove temp file
|
||||||
|
|
|
@ -132,3 +132,16 @@ test("avatarImg", function() {
|
||||||
blank(Discourse.Utilities.avatarImg({avatarTemplate: "", size: 'tiny'}),
|
blank(Discourse.Utilities.avatarImg({avatarTemplate: "", size: 'tiny'}),
|
||||||
"it doesn't render avatars for invalid avatar template");
|
"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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue