uppy/src/plugins/ThumbnailGenerator/index.js
Renée Kooi 76dc0dc3c8
Regenerate thumbnails after restore.
The `generatePreview()` method that GoldenRetriever used to create new
thumbnails after restoring files no longer exists. This patch makes the
ThumbnailGenerator go through all files after a restore, and tries to
generate a thumbnail for all blob URLs (http:// URLs from remote sources
don't need to be regenerated).
2018-03-22 15:09:08 +01:00

218 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
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 doesnt 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()
}
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)
}
}