Skip to content

Commit

Permalink
Finish uplaod resize function (#137)
Browse files Browse the repository at this point in the history
configurable, some fixes, polyfill for chrome
  • Loading branch information
onli committed May 8, 2014
1 parent c49324e commit ccbfcf2
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 62 deletions.
7 changes: 7 additions & 0 deletions include/tpl/config_local.inc.php
Expand Up @@ -594,6 +594,13 @@
'permission' => 'blogConfiguration',
'default' => ''),

array('var' => 'uploadResize',
'title' => MEDIA_UPLOAD_RESIZE,
'description' => MEDIA_UPLOAD_RESIZE_DESC,
'type' => 'bool',
'permission' => 'siteConfiguration',
'default' => false),

array('var' => 'onTheFlySynch',
'title' => ONTHEFLYSYNCH,
'description' => ONTHEFLYSYNCH_DESC,
Expand Down
120 changes: 120 additions & 0 deletions templates/2k11/admin/js/canvas-toBlob.js
@@ -0,0 +1,120 @@
/* canvas-toBlob.js
* A canvas.toBlob() implementation.
* 2013-12-27
*
* By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr
* License: X11/MIT
* See LICENSE.md
*/

/*global self */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
plusplus: true */

/*! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js */

(function(view) {
"use strict";
var
Uint8Array = view.Uint8Array
, HTMLCanvasElement = view.HTMLCanvasElement
, canvas_proto = HTMLCanvasElement && HTMLCanvasElement.prototype
, is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i
, to_data_url = "toDataURL"
, base64_ranks
, decode_base64 = function(base64) {
var
len = base64.length
, buffer = new Uint8Array(len / 4 * 3 | 0)
, i = 0
, outptr = 0
, last = [0, 0]
, state = 0
, save = 0
, rank
, code
, undef
;
while (len--) {
code = base64.charCodeAt(i++);
rank = base64_ranks[code-43];
if (rank !== 255 && rank !== undef) {
last[1] = last[0];
last[0] = code;
save = (save << 6) | rank;
state++;
if (state === 4) {
buffer[outptr++] = save >>> 16;
if (last[1] !== 61 /* padding character */) {
buffer[outptr++] = save >>> 8;
}
if (last[0] !== 61 /* padding character */) {
buffer[outptr++] = save;
}
state = 0;
}
}
}
// 2/3 chance there's going to be some null bytes at the end, but that
// doesn't really matter with most image formats.
// If it somehow matters for you, truncate the buffer up outptr.
return buffer;
}
;
if (Uint8Array) {
base64_ranks = new Uint8Array([
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1
, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35
, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
]);
}
if (HTMLCanvasElement && !canvas_proto.toBlob) {
canvas_proto.toBlob = function(callback, type /*, ...args*/) {
if (!type) {
type = "image/png";
} if (this.mozGetAsFile) {
callback(this.mozGetAsFile("canvas", type));
return;
}
var
args = Array.prototype.slice.call(arguments, 1)
, dataURI = this[to_data_url].apply(this, args)
, header_end = dataURI.indexOf(",")
, data = dataURI.substring(header_end + 1)
, is_base64 = is_base64_regex.test(dataURI.substring(0, header_end))
, blob
;
if (Blob.fake) {
// no reason to decode a data: URI that's just going to become a data URI again
blob = new Blob
if (is_base64) {
blob.encoding = "base64";
} else {
blob.encoding = "URI";
}
blob.data = data;
blob.size = data.length;
} else if (Uint8Array) {
if (is_base64) {
blob = new Blob([decode_base64(data)], {type: type});
} else {
blob = new Blob([decodeURIComponent(data)], {type: type});
}
}
callback(blob);
};

if (canvas_proto.toDataURLHD) {
canvas_proto.toBlobHD = function() {
to_data_url = "toDataURLHD";
var blob = this.toBlob();
to_data_url = "toDataURL";
return blob;
}
} else {
canvas_proto.toBlobHD = canvas_proto.toBlob;
}
}
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
3 changes: 2 additions & 1 deletion templates/2k11/admin/media_upload.tpl
Expand Up @@ -91,4 +91,5 @@
</div>
</form>
<script src="{serendipity_getFile file='admin/js/jquery.cookie.js'}"></script>
<script src="{serendipity_getFile file='admin/js/jquery.tabs.js'}"></script>
<script src="{serendipity_getFile file='admin/js/jquery.tabs.js'}"></script>
<script src="{serendipity_getFile file='admin/js/canvas-toBlob.js'}"></script>
146 changes: 86 additions & 60 deletions templates/2k11/admin/serendipity_editor.js.tpl
Expand Up @@ -1304,66 +1304,92 @@ $(function() {
}

// minify images before upload, approach taken from https://github.com/joelvardy/javascript-image-upload/
// if ($('#uploadform').length > 0) {
// $('#uploadform').submit(function(event) {
// event.preventDefault();
// $('.uploadform_userfile').each(function() {
// var files = this.files;
// for (var i = 0; i < files.length; i++) {
// var reader = new FileReader();
// var file = files[i];
// reader.onload = function(readerEvent) {
// var image = new Image();
// image.onload = function (imageEvent) {
// // Resize image
// var canvas = document.createElement('canvas'),
// max_size = 1200,
// width = image.width,
// height = image.height;
// if (width > height) {
// if (width > max_size) {
// height *= max_size / width;
// width = max_size;
// }
// } else {
// if (height > max_size) {
// width *= max_size / height;
// height = max_size;
// }
// }
// canvas.width = width;
// canvas.height = height;
// canvas.getContext('2d').drawImage(image, 0, 0, width, height);
// var data = new FormData();
// data.append('serendipity[action]', 'admin');
// data.append('serendipity[adminModule]', 'media');
// data.append('serendipity[adminAction]', 'add');
// data.append('serendipity[token]', $('input[name*="serendipity[token]"]').val());
// data.append('serendipity[target_filename][1]', file.name);
// canvas.toBlob(function(blob) {
// data.append('serendipity[userfile][1]', blob, file.name);
// var jqxhr = $.ajax({
// type: 'post',
// url: $('#uploadform').attr('action'),
// data: data,
// cache: false,
// processData: false,
// contentType: false
// }).done(function(data) {
// alert('success');
// }).fail(function(data) {
// alert("fail:" + data.statusText);
// });

// }, file.type);
// }
// image.src = readerEvent.target.result;
// }
// reader.readAsDataURL(file);
// }
// });
// });
// }
{if $uploadResize && ($maxImgWidth > 0 || $maxImgHeight > 0)}
if ($('#uploadform').length > 0) {
$('input[name="go_properties"]').hide();
var progressIcon = document.createElement('span');
progressIcon.className = 'icon-info-circled'
$('#uploadform').submit(function(event) {
event.preventDefault();
$('.uploadform_userfile').each(function() {
var files = this.files;
for (var i = 0; i < files.length; i++) {
var reader = new FileReader();
reader.file = files[i];
reader.onload = function(readerEvent) {
var image = new Image();
var file = this.file;
image.onload = function (imageEvent) {
var canvas = document.createElement('canvas'),
max_width = {if $maxImgWidth}{$maxImgWidth}{else}0{/if},
max_height = {if $maxImgHeight}{$maxImgHeight}{else}0{/if},
width = image.width,
height = image.height;

if (max_width > 0 && width > max_width) {
height *= max_width / width;
width = max_width;
}
if (max_height > 0 && height > max_height) {
width *= max_height / height;
height = max_height;
}

canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
var data = new FormData();
data.append('serendipity[action]', 'admin');
data.append('serendipity[adminModule]', 'media');
data.append('serendipity[adminAction]', 'add');
data.append('serendipity[token]', $('input[name*="serendipity[token]"]').val());
data.append('serendipity[target_filename][1]', file.name);
var type = file.type;
if (type == "image/bmp") {
{* bmp is not supported *}
type = "image/png";
data.append('serendipity[target_filename][1]', file.name.replace('.bmp', '.png'));
}
canvas.toBlob(function(blob) {
data.append('serendipity[userfile][1]', blob, file.name);
var progress = document.createElement('progress');
var progressContainer = document.createElement('span');
progressContainer.className = 'msg_notice';
progress.max = 100;
progress.value = 0;
$(progressContainer).append(progressIcon);
progressContainer.innerHTML += file.name + ": "
$(progressContainer).append(progress);
$('.form_buttons').append(progressContainer);
$.ajax({
type: 'post',
url: $('#uploadform').attr('action'),
data: data,
cache: false,
processData: false,
contentType: false,
xhrFields: {
onprogress: function (e) {
if (e.lengthComputable) {
progress.value = e.loaded / e.total * 100;
}
}
}
}).done(function(data) {
progress.value = 100;
}).fail(function(data) {
alert("fail:" + data.statusText);
})
}, type);
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(reader.file);
}
});
});
}
{/if}

// reopen detail element after spamblock action
if ($('#serendipity_comments_list').length > 0 && window.location.hash && $('#' + window.location.hash.replace('#', '')).length > 0) {
Expand Down
7 changes: 6 additions & 1 deletion templates/2k11/config.inc.php
Expand Up @@ -112,7 +112,12 @@ function serendipity_plugin_api_pre_event_hook($event, &$bag, &$eventData, &$add
switch ($eventData) {
case 'admin/serendipity_editor.js':
header('Content-Type: application/javascript');
$data = array('token_url' => serendipity_setFormToken("url"));
global $serendipity;
$data = array( 'token_url' => serendipity_setFormToken("url"),
'uploadResize' => $serendipity['uploadResize'],
'maxImgWidth' => $serendipity['maxImgWidth'],
'maxImgHeight' => $serendipity['maxImgHeight']
);
echo serendipity_smarty_show('admin/serendipity_editor.js.tpl', $data);
break;
}
Expand Down

13 comments on commit ccbfcf2

@onli
Copy link
Member Author

@onli onli commented on ccbfcf2 May 8, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yellowled Is there a better way to include the polyfill?

@garvinhicking If we want to keep that, there is still the documentation I would do and the language constants to add (which I would prefer if you could do)

@yellowled
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean by “better way”. We don't have a backend mechanism for combine/minify yet.

The only other way I can think of would be to use a conditional script loader to only load it if it's actually required (using a Modernizr test to determine that first). Not sure if we want to add a conditional script loader on top of our existing JS. Might be a good topic for the dev talk.

@onli
Copy link
Member Author

@onli onli commented on ccbfcf2 May 8, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using a Modernizr test to determine that first)

Yeah, that was the direction I was thinking. And, if there is a build-skript to manage those dependencies (grunt-style). Let's talk about it in the devtalk.

@garvinhicking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the required lang constants with a first text to addlang.txt and I'll commit it to the language files (I don't know what you want inside the text g)

@onli
Copy link
Member Author

@onli onli commented on ccbfcf2 May 14, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@garvinhicking I did that in d0b65f2

@garvinhicking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this currently work for you? I can't get it to work; I believe I had reported this at some place, it seems the JS code is not parsed into the actual backend JS (index.php?/plugin/admin/serendipity_editor.js)

@onli
Copy link
Member Author

@onli onli commented on ccbfcf2 May 22, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have a look. But the code is now part of the core and of 2k11, it should not be possible that the setting is ignored, as long as the plugin-api works.

@garvinhicking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@onli I believe the current problem is that serendipity_editor.js is statically delivered from templates/default/ and it is not delivered dynamically by parsing 2k11/serendipity_editor.js.tpl. Don't know how the whole mechanism is properly meant to work though. I believe it already didn't work before I implemented the frontend/backend separation, but I'm not 100% sure about that.

@onli
Copy link
Member Author

@onli onli commented on ccbfcf2 May 22, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would explain it. The idea of the last version was to never¹ fall back to the static version, as the smarty-parsing is implemented in the core, in 694b446

1: with the possible exception of the installer, that I didn't test yet.

@garvinhicking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@onli - What could we do about that? Currently it seems to me that my install properly parses the serendipity_editor.js.tpl, but still the AJAX resizing feature is not working on my installation, the code doesn't show up...

@onli
Copy link
Member Author

@onli onli commented on ccbfcf2 May 26, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is strange, as it works on my system - maybe I just failed to commit something necessary.
Does it never work for you, regardless of the backend template? The configuration value gets saved though?

@garvinhicking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I looked more thoroughly. My settings for image width/height (just above the config option) were set to an empty string. In that case the functionality does not work. Is this how it's supposed to work? Maybe the info-text of the upload-resize option should mention that it relies on the previous two settings?

@onli
Copy link
Member Author

@onli onli commented on ccbfcf2 May 26, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is how it is supposed to work, one of the two has to be set. And sure, if I didn't mention that, that is an oversight on my part and should be fixed.

I'm happy though it is nothing more fundamental with the backend-system.

Please sign in to comment.