uppy
Version:
Extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:
204 lines (179 loc) • 5.29 kB
JavaScript
const Plugin = require('../../core/Plugin')
const Utils = require('../../core/Utils')
/**
* The Thumbnail Generator plugin
*
*/
module.exports = class ThumbnailGenerator extends Plugin {
constructor (uppy, opts) {
super(uppy, opts)
this.type = 'thumbnail'
this.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)
}
/**
* 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.src = 'about:blank'
image.width = 1
image.height = 1
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)
var steps = Math.ceil(Math.log2(image.width / targetWidth))
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 Utils.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) {
const { files } = this.uppy.state
this.uppy.setState({
files: Object.assign({}, files, {
[fileID]: Object.assign({}, files[fileID], {
preview: preview
})
})
})
}
addToQueue (item) {
this.queue.push(item)
if (this.queueProcessing === false) {
this.processQueue()
}
}
processQueue () {
this.queueProcessing = true
if (this.queue.length > 0) {
const current = this.queue.shift()
return this.requestThumbnail(current)
.catch(err => {}) // eslint-disable-line handle-callback-err
.then(() => this.processQueue())
} else {
this.queueProcessing = false
}
}
requestThumbnail (file) {
if (Utils.isPreviewSupported(file.type) && !file.isRemote) {
return this.createThumbnail(file, this.opts.thumbnailWidth)
.then(preview => {
this.setPreviewURL(file.id, preview)
})
.catch(err => {
console.warn(err.stack || err.message)
})
}
return Promise.resolve()
}
install () {
this.uppy.on('file-added', this.addToQueue)
}
uninstall () {
this.uppy.off('file-added', this.addToQueue)
}
}