Skip to content

Instantly share code, notes, and snippets.

@stephanedeluca
Created August 6, 2019 10:32
Show Gist options
  • Save stephanedeluca/567e36ec26202c95ddef54ee4ca37c3d to your computer and use it in GitHub Desktop.
Save stephanedeluca/567e36ec26202c95ddef54ee4ca37c3d to your computer and use it in GitHub Desktop.
Image upload for javascript
//﷽
// zid.upload.js: Image upload
// Derived from ALADDIN work by Stéphane de Luca on Tuesday July 13th 2017
// Copyright ALADDIN © 2013 by Stéphane de Luca
// Release notes:
// — v1b0: Tuesday July 13th 2017 15h47: Initial version.
// — v1b1: Tuesday July 13th 2017 21h50: support M2S url publising.
// — v1b2: Wednesday July 26th 2017 14h24 @ Paris: support max size error.
// — v1b3: Thursday July 27th 2017 10h58 @ Paris les Halles chez Monop : Fixed error propagation.
// — v1b4: Saturday July 29th 2017 03h10 @ Paris: Refactor to Promises (wip).
// — v1b5: Wednesday August 2nd 2017 15h02 @ Saint-Jean du Bruel: Resize, compression and rotation handling.
// — v1b6: Friday August 4th 2017 15h02 @ Saint-Jean: upload fixed.
// — v1b7: Thirsday August 24th, 2017 16h10 @ Tanger: Handle new version of WS that returns id in additon to url.
/*
ZEENS upload protocol:
1– Téléversement d’un fichier sur l’infrastructure de téléversement.
Request
URL : https://content.zeens.ws/api/v1/images
METHOD : POST
BODY : {"base64": "IMGBASE64", "contentType": "image/png"}
Response
{"result": { « contentUrl": "http://url.to.image/file.name"} }
2 – Attachement d'une photo à un concours
L’usage de ce web service doit être authorisé par l’une des trois méthode d’authentification
disponibles (Basic, Facebook et Annymous), ce qui permet de gar der la trace de l’utilisateur
à l’origine du téléversement.
Cependant, il supporte aussi le mode dépourvu d’authorisation, avec la conséquence que l’origine du téléversement demeure inconnue.
Request
URL : https://preprod-m2s.zeens.ws/api/v1/contests/${contestId}/photo/attach
METHOD : PUT
BODY : {« urlIdentifier": "http://url.to.image/file.name"}
Response
{"results": "http://final.url.to.image/file.name"}
*/
class Upload {
setup(tag, wsAuthKeyValue, wsAuthTokenValue, wsHost) {
var self = this;
self.tag = tag;
// http WS setup
self.wsHost = wsHost;
self.wsAuthKeyValue = wsAuthKeyValue;
self.wsAuthTokenValue = wsAuthTokenValue||'';
}
// from http://stackoverflow.com/a/32490603
getOrientation(file, callback) {
var reader = new FileReader();
reader.onload = function(event) {
var view = new DataView(event.target.result);
if (view.getUint16(0, false) != 0xFFD8) return callback(-2);
var length = view.byteLength,
offset = 2;
while (offset < length) {
var marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xFFE1) {
if (view.getUint32(offset += 2, false) != 0x45786966) {
return callback(-1);
}
var little = view.getUint16(offset += 6, false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++)
if (view.getUint16(offset + (i * 12), little) == 0x0112)
return callback(view.getUint16(offset + (i * 12) + 8, little));
}
else if ((marker & 0xFF00) != 0xFF00) break;
else offset += view.getUint16(offset, false);
}
return callback(-1);
};
reader.readAsArrayBuffer(file.slice(0, 64 * 1024));
}
/*
resetOrientation(srcBase64, srcOrientation, callback) {
var img = new Image();
img.onload = function() {
var width = img.width,
height = img.height,
canvas = document.createElement('canvas'),
ctx = canvas.getContext("2d");
// set proper canvas dimensions before transform & export
if ([5,6,7,8].indexOf(srcOrientation) > -1) {
canvas.width = height;
canvas.height = width;
} else {
canvas.width = width;
canvas.height = height;
}
// transform context before drawing image
switch (srcOrientation) {
case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
case 3: ctx.transform(-1, 0, 0, -1, width, height ); break;
case 4: ctx.transform(1, 0, 0, -1, 0, height ); break;
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
case 6: ctx.transform(0, 1, -1, 0, height , 0); break;
case 7: ctx.transform(0, -1, -1, 0, height , width); break;
case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
default: ctx.transform(1, 0, 0, 1, 0, 0);
}
// draw image
ctx.drawImage(img, 0, 0);
// export base64
callback(canvas.toDataURL());
};
}
*/
upload(fileSelector, progressSelector){
var self = this;
var deferred = $.Deferred();
function defer() {
stopLoading(self.tag);
$(progressSelector).removeClass('zid-open');
$(progressSelector).css("width",0);
}
function resolve() {
defer();
console.log(`Upload.upload resolve() with temporaryUploadUrl= ${self.temporaryUploadUrl}`);
return deferred.resolve(self.temporaryUploadUrl);
}
function reject(message) {
defer();
console.log(`Upload.upload reject() with message = ${message}`);
return deferred.reject(message);
}
startLoading(self.tag);
$(`${progressSelector}>div`).css("width",0);
$(progressSelector).addClass('zid-open');
self.temporaryUploadUrl = null;
var input = $(fileSelector);
console.log(`Upload.upload: entering, looking for file in ${fileSelector} (${_file})`);
var _file = input[0];
if(_file.files.length === 0){
console.log(`upload: no file to upload`);
var message = `Aucun fichier n’a été spécifié.`;
$('#blurFactory-error-response').html(message).css({display:'block'});
reject(message);
return;
}
// Fetch image src
var url = window.URL || window.webkitURL;
var file = _file.files[0];
// Prevent any non-image file type from being read.
if(!file.type.match(/image.*/)){
console.log(`Upload.upload: file is not an image ${file.type}`);
var message = `Le fichier que vous avez déposé ne semble pas être un type d’image reconnu.\nDétails de l’erreur : type = ${file.type}, fichier = ${JSON.stringify(file)}.`;
$('#blurFactory-error-response').html(message).css({display:'block'});
reject(message);
return;
}
// check size to prevent uploading large files
var maxSize = 5*1024*1024;
var size = file.size;
console.log(`Upload.upload: file size is ${memorySize(size)}`);
//alert(`size is ${size} against max size is ${maxSize}`);
if (size>maxSize) {
var sMaxSize = memorySize(maxSize);
console.log(`Upload.upload: file size exceeds limit ${sMaxSize} (${memorySize(size)})`);
var message = `La taille de votre photo est de ${memorySize(size)}, ce qui excède la limitee de ${sMaxSize}.\nRéduisez sa taille ou choisissez-en une autre.`;
$('#blurFactory-error-response').html(message).css({display:'block'});
deferred.reject(message);
return;
}
var src = url.createObjectURL(file);
if(!src){
console.log(`Upload.upload: unable to create in-memory object`);
var message = `Il a été impossible de créer l’image en mémoire.\nDétails de l’erreur : url.createObjectURL(file) rend null.`;
$('#blurFactory-error-response').html(message).css({display:'block'});
reject(message);
return;
}
console.log(`Upload.upload(): about to getOrientation of image : ${src}`);
self.getOrientation(file, function(srcOrientation) {
console.log(`Upload.upload(): orientation of image is ${srcOrientation}`);
var contentType = 'image/jpeg';
// Load image
var p1 = imageLoad(src)
//self.uploadAndAttach(src)
.done (function(image) {
var base64Data = null;
try {
base64Data = imageBase64RotateResize(image, srcOrientation, contentType);
}
catch(error) {
console.log(`uploadBase64Image try catch failed: ${JSON.stringify(error)}`);
reject(`uploadBase64Image try catch failed: ${error.name}, ${error.message}`);
}
// Start upload protocol to the server
self.uploadBase64Image(base64Data, contentType, progressSelector)
.done(function(){
resolve();
})
.fail(function(error){
console.log(`uploadBase64Image failed: ${JSON.stringify(error)}`);
reject(`uploadBase64Image failed: ${error.name}, ${error.message}`);
});
}) // imagePromise().done()
.fail(function(error) {
console.log(`imageLoad failed: ${JSON.stringify(error)}`);
reject(`imageLoad failed: ${error.name}, ${error.message}`);
});
}); // getOrientation()
/*
var p2 = p1.then(function(image) {
var base64Data = null;
try {
base64Data = imageBase64RotateResize(image);
}
catch(error) {
console.log(`uploadBase64Image try catch failed: ${JSON.stringify(error)}`);
reject(`uploadBase64Image try catch failed: ${error.name}, ${error.message}`);
}
// Start upload protocol to the server
return self.uploadBase64Image(base64Data, contentType, progressSelector, resolve);
});
p2.done(function(){
resolve();
})
.fail(function(error){
console.log(`uploadBase64Image failed: ${JSON.stringify(error)}`);
reject(`uploadBase64Image failed: ${error.name}, ${error.message}`);
});
*/
return deferred.promise();
} // upload()
uploadBase64Image(base64Data, contentType, progressSelector) {
var self = this;
console.log(`Upload.uploadBase64Image(): about to upload base 64 data of size of ${memorySize(base64Data.length)}…`);
// 1 - upload
var url = 'https://content.zeens.ws/api/v1/images';
var method = 'POST';
//console.log(`upload: posting photo to server ${url} with ${method}`);
// now upload
return $.ajax({
url: url,
data: `{base64: "${base64Data}", contentType: "${contentType}"}`,
cache: false,
contentType: 'application/json',
processData: false,
type: method,
dataType: 'json',
xhr: function () {
var xhr = new window.XMLHttpRequest();
var i=0;
// upload progress
xhr.upload.addEventListener("progress", function (e) {
if (e.lengthComputable && e.total) {
var progress = Math.round(e.loaded*100.0/e.total);
if (parseInt(progress)%10 == 0) {
console.log(`Upload.uploadBase64Image(): progress ${i} is ${progress}% loaded=${memorySize(e.loaded)} out of ${memorySize(e.total)}`);
}
i += 1;
$(`${progressSelector}>div`).css('width', progress + '%');
}
}, false);
return xhr;
},
success: function(json){
//console.log(`Upload.upload success: ${JSON.stringify(json)}`);
//{"result": { « contentUrl": "http://url.to.image/file.name"} }
self.temporaryUploadUrl = json.result.contentUrl;
},
error: function(error) {
console.log(`*** error Upload.uploadBase64Image : ${error.statusText}`);
self.temporaryUploadUrl = null;
},
onreadystatechange: function(){
//console.log(`Upload.upload: request readyState is ${request.readyState}`);
if(request.readyState == 4){
try {
var json = JSON.parse(request.response);
/*{
"result": {
"contentUrl": "http://url.to.image/file.name"
}
}
*/
var result = json.result;
var url = result.contentUrl;
$("#photoUploadResult").html("envoi terminé…");
alert(`Nous allons examiner votre photo dans les meilleurs délais afin de pouvoir la publier (${url})`);
} catch (e){
var resp = {
status: 'error',
data: `Votre photo n’a pu être transmise au serveur : [${request.responseText}]`,
};
$("#photoUploadResult").html("en cours d’envoi…")
alert("L’envoi n’est pas parvenu au serveur. Vérifier que votre connexion Internet fonctionne correctement et essayez à nouveau");
}
//console.log(`upload: ${resp.status}: ${resp.data}`);
}
},
});
}
attachUrlToContest(contestId, urlToPublish, completed) {
var self = this;
// 2 - publish
var url = `${self.wsHost}/api/v1/contests/${contestId}/photo/attach`;
var method = 'PUT';
/*console.log(`Upload.attachUrlToContest: attach ${urlToPublish} to server ${url} with ${method}
with data {urlIdentifier:"${urlToPublish}", issueId:"${contestId}"}
with header Authorization: ${self.wsAuthKeyValue}
and ProviderToken: ${self.wsAuthTokenValue}
`);
*/
startLoading(self.tag);
// now upload
self.errorMessage = null;
return $.ajax({
url: url,
type: method,
headers: {
'Authorization': self.wsAuthKeyValue,
'ProviderToken': self.wsAuthTokenValue,
'Content-Type': 'application/json'
},
data: `{urlIdentifier:"${urlToPublish}"}`,
cache: false,
contentType: 'application/json',
processData: false,
dataType: 'json',
dataFilter: function (text, type) {
var json = JSON.parse(text);
//console.log(`type:${type}, ${JSON.stringify(json)}`);
var result = json.result;
//console.log(`data filter returns: ${JSON.stringify(result)}`);
return JSON.stringify(result);
},
/*
success: function(json){
console.log(`Upload.attachUrlToContest success : ${JSON.stringify(json)}`);
},*/
error: function(error) {
self.errorMessage = `*** error Upload.attachUrlToContest: ${error.statusText}`;
console.log(self.errorMessage); console.log(error);
}
});
} // publish
/*
uploadAndAttach(src) {
var loadedPhoto = imagePromise(src);
var upoadedPhoto = loadedPhoto.then(function(){
return $.ajax({
url: '/path/to/another/file',
dataType: 'json',
data: data.sessionID
});
});
var attachedPhoto = upoadedPhoto.then(function(){
return $.ajax({
url: '/path/to/another/file',
dataType: 'json',
data: data.sessionID
});
});
return attachedPhoto;
// var a1 = $.ajax({
// url: '/path/to/file',
// dataType: 'json'
// }),
// a2 = a1.then(function(data) {
// // .then() returns a new promise
// return $.ajax({
// url: '/path/to/another/file',
// dataType: 'json',
// data: data.sessionID
// });
// });
//
// a2.done(function(data) {
// console.log(data);
// });
}
*/
}
Upload.tattoo = function() {
var c = {name: "Upload", version: 1, build: 7, date: "24/08/2017", time: "16h11"};
c.label = `${c.name} v${c.version}b${c.build} (${c.date} ${c.time})`;
return c;
}
/*
<a href="#">Click me!</a>
<div></div>
$.when(
getTweets('austintexasgov'),
getTweets('greenling_com'),
getTweets('themomandpops')
).done(function(atxArgs, greenlingArgs, momandpopsArgs){
var allTweets = [].concat(atxArgs[0]).concat(greenlingArgs[0]).concat(momandpopsArgs[0]);
var sortedTweets = sortTweets(allTweets);
showTweets(sortedTweets);
});
var getTweets = function(user){
var url='http://twitter.com/status/user_timeline/' + user + '.json';
return $.get(url, {count:5}, null, 'jsonp');
}
*/
/*
function GetSomeDeferredStuff() {
var deferreds = [];
var i = 1;
for (i = 1; i <= 10; i++) {
var count = i;
deferreds.push(
$.post('/echo/html/', {
html: "<p>Task #" + count + " complete.",
delay: count
}).success(function(data) {
$("div").append(data);
})
);
}
return deferreds;
}
$(function() {
$("a").click(function() {
var deferreds = GetSomeDeferredStuff();
$.when.apply(null, deferreds).done(function() {
$("div").append("<p>All done!</p>");
});
});
});
*/
function imageslLoad(assets) {
var promises = [];
for (var i = 0; i < assets.length; i++) {
var promise = imagePromise(assets[i]);
promises.push(promise);
}
$.when.apply($, promises).done(function () {
// do something with the images
});
}
function imageLoad(src) {
var deferred = $.Deferred();
var img = new Image();
function resolve() {
// Resolution callbacks receive the image, which you can then inject into the DOM
// to avoid triggering an extra HTTP request in the browser
return deferred.resolve(img);
}
function reject(a) {
console.log(`imageLoad(${src}): rejected with ${JSON.stringify(a)}`);
return deferred.reject(a);
}
// Resolution events
$(img).on({error: reject, load: resolve});
// Attach the source afterwards, since DOM synchronicity is weird:
// A cached image will sometimes load or error on assignment
img.src = src;
return deferred.promise();
}
function imageBase64RotateResize(image, srcOrientation, contentType) {
// --- Potentially rotate it
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var width = image.width;
var height = image.height;
//var ratio = width/height;
var maxWidthHeight = 1024;
var s = 1.0;
var debug4Anas = true;
var dontResizeNorCrop = false;
/*
if (debug4Anas && srcOrientation > 1) {
dontResizeNorCrop = true;
console.log(`************ Log4Anas: uploadBase64Image succeeded, base64…`);
}
*/
if (dontResizeNorCrop) {
canvas.width = width;
canvas.height = height;
}
else {
// resize, if required
if (width>maxWidthHeight) {
s = maxWidthHeight / width;
width = maxWidthHeight;
height *= s;
//height = width / ratio;
}
else if (height>maxWidthHeight) {
s = maxWidthHeight / height;
height = maxWidthHeight;
width *= s;
//width = height * ratio;
}
// set proper canvas dimensions before transform & export
if ([5,6,7,8].indexOf(srcOrientation) > -1) {
canvas.width = height;
canvas.height = width;
} else {
canvas.width = width;
canvas.height = height;
}
// transform context before drawing image
switch (srcOrientation) {
case 2: context.transform(-s, 0, 0, s, width, 0); break; // vertical-axis symetry
case 3: context.transform(-s, 0, 0, -s, width, height); break; // 180 rotate
case 4: context.transform( s, 0, 0, -s, 0, height); break;
case 5: context.transform( 0, s, s, 0, 0, 0); break;
case 6: context.transform( 0, s, -s, 0, height, 0); break;
case 7: context.transform( 0, -s, -s, 0, height, width); break;
case 8: context.transform( 0, -s, s, 0, 0, width); break;
default: context.transform( s, 0, 0, s, 0, 0); break;
}
console.log(`Upload.upload(): resiezd to 1024x1024 and rotated according to orientation ${srcOrientation}`);
}
context.drawImage(image, 0, 0);
// --- Turn it into base64
var myData = context.getImageData(0, 0, width, height);
var base64Data = canvas.toDataURL(contentType);
/*
if (debug4Anas && srcOrientation > 1) {
console.log(`************ Log4Anas: uploadBase64Image succeeded, base64 is: ${base64Data}`);
}
*/
//$("#upload").css({"background-image": `"${base64Data}"`});
// --- Remove unwanted header
base64Data = base64Data.replace(/^.*?,/g,'');
console.log(`Upload.upload : about to upload image as base64 ${contentType}: ${base64Data.substr(0, 32)}`);
return base64Data;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment