Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resize images on client-side before uploading #7734

Merged
merged 1 commit into from Apr 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -113,6 +113,7 @@ source "https://rails-assets.org" do
gem "rails-assets-corejs-typeahead", "1.1.1"
gem "rails-assets-cropperjs", "1.2.1"
gem "rails-assets-fine-uploader", "5.13.0"
gem "rails-assets-pica", "4.0.1"

# jQuery plugins

Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Expand Up @@ -558,6 +558,7 @@ GEM
rails-assets-markdown-it-sub (1.0.0)
rails-assets-markdown-it-sup (1.0.0)
rails-assets-perfect-scrollbar (0.6.16)
rails-assets-pica (4.0.1)
rails-assets-underscore (1.8.3)
rails-controller-testing (1.0.2)
actionpack (~> 5.x, >= 5.0.1)
Expand Down Expand Up @@ -878,6 +879,7 @@ DEPENDENCIES
rails-assets-markdown-it-sub (= 1.0.0)!
rails-assets-markdown-it-sup (= 1.0.0)!
rails-assets-perfect-scrollbar (= 0.6.16)!
rails-assets-pica (= 4.0.1)!
rails-controller-testing (= 1.0.2)
rails-i18n (= 5.0.4)
rails-timeago (= 2.16.0)
Expand Down
83 changes: 27 additions & 56 deletions app/assets/javascripts/app/views/publisher/uploader_view.js
Expand Up @@ -5,65 +5,19 @@
// progress. Attaches previews of finished uploads to the publisher.

app.views.PublisherUploader = Backbone.View.extend({
allowedExtensions: ["jpg", "jpeg", "png", "gif"],
sizeLimit: 4194304, // bytes

initialize: function(opts) {
this.publisher = opts.publisher;
this.uploader = new qq.FineUploaderBasic({
element: this.el,
button: this.el,

text: {
fileInputTitle: Diaspora.I18n.t("photo_uploader.upload_photos")
},
request: {
endpoint: Routes.photos(),
params: {
/* eslint-disable camelcase */
authenticity_token: $("meta[name='csrf-token']").attr("content"),
/* eslint-enable camelcase */
photo: {
pending: true
}
}
},
validation: {
allowedExtensions: this.allowedExtensions,
sizeLimit: this.sizeLimit
},
messages: {
typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
emptyError: Diaspora.I18n.t("photo_uploader.empty")
},
callbacks: {
onProgress: _.bind(this.progressHandler, this),
onSubmit: _.bind(this.submitHandler, this),
onComplete: _.bind(this.uploadCompleteHandler, this),
onError: function(id, name, errorReason) {
if (app.flashMessages) { app.flashMessages.error(errorReason); }
}
}
});

this.info = $("<div id=\"fileInfo\" />");
this.publisher.wrapperEl.before(this.info);

this.publisher.photozoneEl.on("click", ".x", _.bind(this._removePhoto, this));
},

progressHandler: function(id, fileName, loaded, total) {
var progress = Math.round(loaded / total * 100);
this.info.text(fileName + " " + progress + "%").fadeTo(200, 1);
this.publisher.photozoneEl
.find("li.loading").first().find(".progress-bar")
.width(progress + "%");
},
// Initialize the PostPhotoUploader and subscribe its events
this.uploader = new Diaspora.PostPhotoUploader(this.el);

submitHandler: function() {
this.$el.addClass("loading");
this._addPhotoPlaceholder();
this.uploader.onUploadStarted = _.bind(this.uploadStartedHandler, this);
this.uploader.onProgress = _.bind(this.progressHandler, this);
this.uploader.onUploadCompleted = _.bind(this.uploadCompleteHandler, this);
},

// add photo placeholders to the publisher to indicate an upload in progress
Expand All @@ -82,14 +36,26 @@ app.views.PublisherUploader = Backbone.View.extend({
);
},

uploadStartedHandler: function() {
this.$el.addClass("loading");
this._addPhotoPlaceholder();
},

progressHandler: function(fileName, progress) {
this.info.text(fileName + " " + progress + "%").fadeTo(200, 1);
this.publisher.photozoneEl
.find("li.loading").first().find(".progress-bar")
.width(progress + "%");
},

uploadCompleteHandler: function(_id, fileName, response) {
if (response.success){
this.info.text(Diaspora.I18n.t("photo_uploader.completed", {file: fileName})).fadeTo(2000, 0);

var id = response.data.photo.id,
url = response.data.photo.unprocessed_image.url;
image = response.data.photo.unprocessed_image;

this._addFinishedPhoto(id, url);
this._addFinishedPhoto(id, image);
this.trigger("change");
} else {
this._cancelPhotoUpload();
Expand All @@ -105,14 +71,13 @@ app.views.PublisherUploader = Backbone.View.extend({

// replace the first photo placeholder with the finished uploaded image and
// add the id to the publishers form
_addFinishedPhoto: function(id, url) {
_addFinishedPhoto: function(id, image) {
var publisher = this.publisher;

// add form input element
publisher.$(".content_creation form").append(
"<input type=\"hidden\", value=\""+id+"\" name=\"photos[]\" />"
);

// replace placeholder
var placeholder = publisher.photozoneEl.find("li.loading").first();
placeholder
Expand All @@ -121,7 +86,13 @@ app.views.PublisherUploader = Backbone.View.extend({
"<div class=\"x\"></div>"+
"<div class=\"circle\"></div>"
)
.find("img").attr({"src": url, "data-id": id}).removeClass("ajax-loader");
.find("img").attr(
{
"src": image.thumb_medium.url,
"data-small": image.thumb_small.url,
"data-scaled": image.scaled_full.url,
"data-id": id
}).removeClass("ajax-loader");
placeholder
.find("div.progress").remove();

Expand Down
8 changes: 4 additions & 4 deletions app/assets/javascripts/app/views/publisher_view.js
Expand Up @@ -284,13 +284,13 @@ app.views.Publisher = Backbone.View.extend({
getUploadedPhotos: function() {
var photos = [];
$("li.publisher_photo img").each(function() {
var file = $(this).attr("src").substring("/uploads/images/".length);
var photo = $(this);
photos.push(
{
"sizes": {
"small" : "/uploads/images/thumb_small_" + file,
"medium" : "/uploads/images/thumb_medium_" + file,
"large" : "/uploads/images/scaled_full_" + file
"small": photo.data("small"),
"medium": photo.attr("src"),
"large": photo.data("scaled")
}
}
);
Expand Down
103 changes: 103 additions & 0 deletions app/assets/javascripts/helpers/post_photo_uploader.es6
@@ -0,0 +1,103 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later

Diaspora.PostPhotoUploader = class {
/**
* Initializes a new instance of PostPhotoUploader
* This class handles uploading photos and provides client side scaling
*/
constructor(el, aspectIds) {
this.element = el;
this.sizeLimit = 4194304;
this.aspectIds = aspectIds;

this.onProgress = null;
this.onUploadStarted = null;
this.onUploadCompleted = null;

/**
* Shows a message using flash messages or alert for mobile.
* @param {string} type - The type of the message, e.g. "error" or "success".
* @param text - The text to display.
*/
this.showMessage = (type, text) => (app.flashMessages ? app.flashMessages[type](text) : alert(text));

/**
* Returns true if the given parameter is a function
* @param {param} - The object to check
* @returns {boolean}
*/
this.func = param => (typeof param === "function");

this.initFineUploader();
}

/**
* Initializes the fine uploader component
*/
initFineUploader() {
this.fineUploader = new qq.FineUploaderBasic({
element: this.element,
button: this.element,
text: {
fileInputTitle: Diaspora.I18n.t("photo_uploader.upload_photos")
},
request: {
endpoint: Routes.photos(),
params: {
/* eslint-disable camelcase */
authenticity_token: $("meta[name='csrf-token']").attr("content"),
photo: {
pending: true,
aspect_ids: this.aspectIds
}
/* eslint-enable camelcase */
}
},
validation: {
allowedExtensions: ["jpg", "jpeg", "png", "gif"],
sizeLimit: (window.Promise && qq.supportedFeatures.scaling ? null : this.sizeLimit)
},
messages: {
typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
emptyError: Diaspora.I18n.t("photo_uploader.empty")
},
callbacks: {
onSubmit: (id, name) => this.onPictureSelected(id, name),
onUpload: (id, name) => (this.func(this.onUploadStarted) && this.onUploadStarted(id, name)),
onProgress: (id, fileName, loaded, total) =>
(this.func(this.onProgress) && this.onProgress(fileName, Math.round(loaded / total * 100))),
onComplete: (id, name, json) => (this.func(this.onUploadCompleted) && this.onUploadCompleted(id, name, json)),
onError: (id, name, errorReason) => this.showMessage("error", errorReason)
}
});
}

/**
* Called when a picture from user's device has been selected.
* Scales the images using Pica if the image exceeds the file size limit
* @param {number} id - The current file's id.
* @param {string} name - The current file's name.
*/
onPictureSelected(id, name) {
// scale image because it's bigger than the size limit and the browser supports it
if (this.fineUploader.getSize(id) > this.sizeLimit && window.Promise && qq.supportedFeatures.scaling) {
this.fineUploader.scaleImage(id, {
maxSize: 3072,
customResizer: !qq.ios() && (i => window.pica().resize(i.sourceCanvas, i.targetCanvas))
}).then(scaledImage => {
this.fineUploader.addFiles({
blob: scaledImage,
name: name
});
});

// since we are adding the smaller scaled image afterwards, we return false
return false;
}

// return true to upload the image without scaling
return true;
}
};
// @license-end
1 change: 1 addition & 0 deletions app/assets/javascripts/main.js
Expand Up @@ -46,3 +46,4 @@
//= require helpers/markdown_editor
//= require jquery.are-you-sure
//= require cropperjs/dist/cropper.js
//= require pica
2 changes: 2 additions & 0 deletions app/assets/javascripts/mobile/mobile.js
Expand Up @@ -14,11 +14,13 @@
//= require jquery.timeago
//= require underscore
//= require bootstrap
//= require pica
//= require diaspora
//= require helpers/i18n
//= require helpers/tags_autocomplete
//= require bootstrap-markdown/bootstrap-markdown
//= require helpers/markdown_editor
//= require helpers/post_photo_uploader
//= require widgets/timeago
//= require mobile/mobile_application
//= require mobile/mobile_file_uploader
Expand Down