Created
August 6, 2019 10:32
-
-
Save stephanedeluca/567e36ec26202c95ddef54ee4ca37c3d to your computer and use it in GitHub Desktop.
Image upload for javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//﷽ | |
// 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