Created
October 10, 2018 05:58
-
-
Save sponnet/39d190316a406b6607e1857f4b1c97d1 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
const { Plugin } = require("@uppy/core"); | |
const dataURItoBlob = require("@uppy/utils/lib/dataURItoBlob"); | |
const isPreviewSupported = require("@uppy/utils/lib/isPreviewSupported"); | |
/** | |
* The Thumbnail Generator plugin | |
* | |
*/ | |
module.exports = class ThumbnailGenerator extends Plugin { | |
constructor(uppy, opts) { | |
super(uppy, opts); | |
this.type = "thumbnail"; | |
this.id = this.opts.id || "ThumbnailGenerator"; | |
this.title = "Thumbnail Generator"; | |
this.queue = []; | |
this.queueProcessing = false; | |
const defaultOptions = { | |
thumbnailWidth: 200 | |
}; | |
this.opts = Object.assign({}, defaultOptions, opts); | |
this.addToQueue = this.addToQueue.bind(this); | |
this.onRestored = this.onRestored.bind(this); | |
} | |
/** | |
* Create a thumbnail for the given Uppy file object. | |
* | |
* @param {{data: Blob}} file | |
* @param {number} width | |
* @return {Promise} | |
*/ | |
createThumbnail(file, targetWidth) { | |
const originalUrl = URL.createObjectURL(file.data); | |
const onload = new Promise((resolve, reject) => { | |
const image = new Image(); | |
image.src = originalUrl; | |
image.onload = () => { | |
URL.revokeObjectURL(originalUrl); | |
resolve(image); | |
}; | |
image.onerror = () => { | |
// The onerror event is totally useless unfortunately, as far as I know | |
URL.revokeObjectURL(originalUrl); | |
reject(new Error("Could not create thumbnail")); | |
}; | |
}); | |
return onload | |
.then(image => { | |
const targetHeight = this.getProportionalHeight(image, targetWidth); | |
const canvas = this.resizeImage(image, targetWidth, targetHeight); | |
return this.canvasToBlob(canvas, "image/png"); | |
}) | |
.then(blob => { | |
return URL.createObjectURL(blob); | |
}); | |
} | |
/** | |
* Make sure the image doesn’t exceed browser/device canvas limits. | |
* For ios with 256 RAM and ie | |
*/ | |
protect(image) { | |
// https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element | |
var ratio = image.width / image.height; | |
var maxSquare = 5000000; // ios max canvas square | |
var maxSize = 4096; // ie max canvas dimensions | |
var maxW = Math.floor(Math.sqrt(maxSquare * ratio)); | |
var maxH = Math.floor(maxSquare / Math.sqrt(maxSquare * ratio)); | |
if (maxW > maxSize) { | |
maxW = maxSize; | |
maxH = Math.round(maxW / ratio); | |
} | |
if (maxH > maxSize) { | |
maxH = maxSize; | |
maxW = Math.round(ratio * maxH); | |
} | |
if (image.width > maxW) { | |
var canvas = document.createElement("canvas"); | |
canvas.width = maxW; | |
canvas.height = maxH; | |
canvas.getContext("2d").drawImage(image, 0, 0, maxW, maxH); | |
image = canvas; | |
} | |
return image; | |
} | |
/** | |
* Resize an image to the target `width` and `height`. | |
* | |
* Returns a Canvas with the resized image on it. | |
*/ | |
resizeImage(image, targetWidth, targetHeight) { | |
// Resizing in steps refactored to use a solution from | |
// https://blog.uploadcare.com/image-resize-in-browsers-is-broken-e38eed08df01 | |
image = this.protect(image); | |
// Use the Polyfill for Math.log2() since IE doesn't support log2 | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2#Polyfill | |
var steps = Math.ceil(Math.log(image.width / targetWidth) * Math.LOG2E); | |
if (steps < 1) { | |
steps = 1; | |
} | |
var sW = targetWidth * Math.pow(2, steps - 1); | |
var sH = targetHeight * Math.pow(2, steps - 1); | |
var x = 2; | |
while (steps--) { | |
var canvas = document.createElement("canvas"); | |
canvas.width = sW; | |
canvas.height = sH; | |
canvas.getContext("2d").drawImage(image, 0, 0, sW, sH); | |
image = canvas; | |
sW = Math.round(sW / x); | |
sH = Math.round(sH / x); | |
} | |
return image; | |
} | |
/** | |
* Save a <canvas> element's content to a Blob object. | |
* | |
* @param {HTMLCanvasElement} canvas | |
* @return {Promise} | |
*/ | |
canvasToBlob(canvas, type, quality) { | |
if (canvas.toBlob) { | |
return new Promise(resolve => { | |
canvas.toBlob(resolve, type, quality); | |
}); | |
} | |
return Promise.resolve().then(() => { | |
return dataURItoBlob(canvas.toDataURL(type, quality), {}); | |
}); | |
} | |
getProportionalHeight(img, width) { | |
const aspect = img.width / img.height; | |
return Math.round(width / aspect); | |
} | |
/** | |
* Set the preview URL for a file. | |
*/ | |
setPreviewURL(fileID, preview) { | |
this.uppy.setFileState(fileID, { | |
preview: preview | |
}); | |
} | |
addToQueue(item) { | |
var preview = new Promise((resolve, reject) => { | |
this.queue.push({ item: item, resolve: resolve }); | |
if (this.queueProcessing === false) { | |
this.processQueue(); | |
} | |
}); | |
this.uppy.setFileState(item.id, { | |
previewPromise: preview | |
}); | |
} | |
processQueue() { | |
this.queueProcessing = true; | |
if (this.queue.length > 0) { | |
const current = this.queue.shift(); | |
return this.requestThumbnail(current.item) | |
.catch(err => {}) // eslint-disable-line handle-callback-err | |
.then(preview => { | |
current.resolve(preview); | |
this.processQueue(); | |
}); | |
} else { | |
this.queueProcessing = false; | |
} | |
} | |
requestThumbnail(file) { | |
if (isPreviewSupported(file.type) && !file.isRemote) { | |
return this.createThumbnail(file, this.opts.thumbnailWidth) | |
.then(preview => { | |
this.setPreviewURL(file.id, preview); | |
return preview; | |
}) | |
.catch(err => { | |
console.warn(err.stack || err.message); | |
}); | |
} | |
return Promise.resolve(); | |
} | |
onRestored() { | |
const fileIDs = Object.keys(this.uppy.getState().files); | |
fileIDs.forEach(fileID => { | |
const file = this.uppy.getFile(fileID); | |
if (!file.isRestored) return; | |
// Only add blob URLs; they are likely invalid after being restored. | |
if (!file.preview || /^blob:/.test(file.preview)) { | |
this.addToQueue(file); | |
} | |
}); | |
} | |
install() { | |
this.uppy.on("file-added", this.addToQueue); | |
this.uppy.on("restored", this.onRestored); | |
} | |
uninstall() { | |
this.uppy.off("file-added", this.addToQueue); | |
this.uppy.off("restored", this.onRestored); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment