Browse Source

@uppy/thumbnail-generator: refactor to ESM (#3734)

Antoine du Hamel 2 years ago
parent
commit
5a2dd50d6c

+ 1 - 0
.eslintrc.js

@@ -219,6 +219,7 @@ module.exports = {
         'packages/@uppy/status-bar/src/**/*.js',
         'packages/@uppy/status-bar/src/**/*.js',
         'packages/@uppy/svelte/src/**/*.js',
         'packages/@uppy/svelte/src/**/*.js',
         'packages/@uppy/svelte/rollup.config.js',
         'packages/@uppy/svelte/rollup.config.js',
+        'packages/@uppy/thumbnail-generator/src/**/*.js',
         'packages/@uppy/tus/src/**/*.js',
         'packages/@uppy/tus/src/**/*.js',
         'packages/@uppy/unsplash/src/**/*.js',
         'packages/@uppy/unsplash/src/**/*.js',
         'packages/@uppy/url/src/**/*.js',
         'packages/@uppy/url/src/**/*.js',

+ 2 - 0
packages/@uppy/thumbnail-generator/package.json

@@ -5,6 +5,7 @@
   "license": "MIT",
   "license": "MIT",
   "main": "lib/index.js",
   "main": "lib/index.js",
   "types": "types/index.d.ts",
   "types": "types/index.d.ts",
+  "type": "module",
   "keywords": [
   "keywords": [
     "file uploader",
     "file uploader",
     "uppy",
     "uppy",
@@ -26,6 +27,7 @@
     "exifr": "^7.0.0"
     "exifr": "^7.0.0"
   },
   },
   "devDependencies": {
   "devDependencies": {
+    "@jest/globals": "^27.4.2",
     "namespace-emitter": "2.0.1"
     "namespace-emitter": "2.0.1"
   },
   },
   "peerDependencies": {
   "peerDependencies": {

+ 114 - 107
packages/@uppy/thumbnail-generator/src/index.js

@@ -1,17 +1,110 @@
-const { UIPlugin } = require('@uppy/core')
-const dataURItoBlob = require('@uppy/utils/lib/dataURItoBlob')
-const isObjectURL = require('@uppy/utils/lib/isObjectURL')
-const isPreviewSupported = require('@uppy/utils/lib/isPreviewSupported')
-const { rotation } = require('exifr/dist/mini.umd.js')
+import { UIPlugin } from '@uppy/core'
+import dataURItoBlob from '@uppy/utils/lib/dataURItoBlob'
+import isObjectURL from '@uppy/utils/lib/isObjectURL'
+import isPreviewSupported from '@uppy/utils/lib/isPreviewSupported'
+import { rotation } from 'exifr/dist/mini.umd.js'
 
 
-const locale = require('./locale')
+import locale from './locale.js'
+import packageJson from '../package.json'
+
+/**
+ * Save a <canvas> element's content to a Blob object.
+ *
+ * @param {HTMLCanvasElement} canvas
+ * @returns {Promise}
+ */
+function canvasToBlob (canvas, type, quality) {
+  try {
+    canvas.getContext('2d').getImageData(0, 0, 1, 1)
+  } catch (err) {
+    if (err.code === 18) {
+      return Promise.reject(new Error('cannot read image, probably an svg with external resources'))
+    }
+  }
+
+  if (canvas.toBlob) {
+    return new Promise(resolve => {
+      canvas.toBlob(resolve, type, quality)
+    }).then((blob) => {
+      if (blob === null) {
+        throw new Error('cannot read image, probably an svg with external resources')
+      }
+      return blob
+    })
+  }
+  return Promise.resolve().then(() => {
+    return dataURItoBlob(canvas.toDataURL(type, quality), {})
+  }).then((blob) => {
+    if (blob === null) {
+      throw new Error('could not extract blob, probably an old browser')
+    }
+    return blob
+  })
+}
+
+function rotateImage (image, translate) {
+  let w = image.width
+  let h = image.height
+
+  if (translate.deg === 90 || translate.deg === 270) {
+    w = image.height
+    h = image.width
+  }
+
+  const canvas = document.createElement('canvas')
+  canvas.width = w
+  canvas.height = h
+
+  const context = canvas.getContext('2d')
+  context.translate(w / 2, h / 2)
+  if (translate.canvas) {
+    context.rotate(translate.rad)
+    context.scale(translate.scaleX, translate.scaleY)
+  }
+  context.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height)
+
+  return canvas
+}
+
+/**
+ * Make sure the image doesn’t exceed browser/device canvas limits.
+ * For ios with 256 RAM and ie
+ */
+function protect (image) {
+  // https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element
+
+  const ratio = image.width / image.height
+
+  const maxSquare = 5000000 // ios max canvas square
+  const maxSize = 4096 // ie max canvas dimensions
+
+  let maxW = Math.floor(Math.sqrt(maxSquare * ratio))
+  let 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) {
+    const canvas = document.createElement('canvas')
+    canvas.width = maxW
+    canvas.height = maxH
+    canvas.getContext('2d').drawImage(image, 0, 0, maxW, maxH)
+    return canvas
+  }
+
+  return image
+}
 
 
 /**
 /**
  * The Thumbnail Generator plugin
  * The Thumbnail Generator plugin
  */
  */
 
 
-module.exports = class ThumbnailGenerator extends UIPlugin {
-  static VERSION = require('../package.json').version
+export default class ThumbnailGenerator extends UIPlugin {
+  static VERSION = packageJson.version
 
 
   constructor (uppy, opts) {
   constructor (uppy, opts) {
     super(uppy, opts)
     super(uppy, opts)
@@ -84,7 +177,7 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
    * account. If neither width nor height are given, the default dimension
    * account. If neither width nor height are given, the default dimension
    * is used.
    * is used.
    */
    */
-  getProportionalDimensions (img, width, height, rotation) {
+  getProportionalDimensions (img, width, height, rotation) { // eslint-disable-line no-shadow
     let aspect = img.width / img.height
     let aspect = img.width / img.height
     if (rotation === 90 || rotation === 270) {
     if (rotation === 90 || rotation === 270) {
       aspect = img.height / img.width
       aspect = img.height / img.width
@@ -110,39 +203,6 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
     }
     }
   }
   }
 
 
-  /**
-   * 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
-
-    const ratio = image.width / image.height
-
-    const maxSquare = 5000000 // ios max canvas square
-    const maxSize = 4096 // ie max canvas dimensions
-
-    let maxW = Math.floor(Math.sqrt(maxSquare * ratio))
-    let 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) {
-      const 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`.
    * Resize an image to the target `width` and `height`.
    *
    *
@@ -152,9 +212,9 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
     // Resizing in steps refactored to use a solution from
     // Resizing in steps refactored to use a solution from
     // https://blog.uploadcare.com/image-resize-in-browsers-is-broken-e38eed08df01
     // https://blog.uploadcare.com/image-resize-in-browsers-is-broken-e38eed08df01
 
 
-    image = this.protect(image)
+    let img = this.protect(image)
 
 
-    let steps = Math.ceil(Math.log2(image.width / targetWidth))
+    let steps = Math.ceil(Math.log2(img.width / targetWidth))
     if (steps < 1) {
     if (steps < 1) {
       steps = 1
       steps = 1
     }
     }
@@ -166,73 +226,14 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
       const canvas = document.createElement('canvas')
       const canvas = document.createElement('canvas')
       canvas.width = sW
       canvas.width = sW
       canvas.height = sH
       canvas.height = sH
-      canvas.getContext('2d').drawImage(image, 0, 0, sW, sH)
-      image = canvas
+      canvas.getContext('2d').drawImage(img, 0, 0, sW, sH)
+      img = canvas
 
 
       sW = Math.round(sW / x)
       sW = Math.round(sW / x)
       sH = Math.round(sH / x)
       sH = Math.round(sH / x)
     }
     }
 
 
-    return image
-  }
-
-  rotateImage (image, translate) {
-    let w = image.width
-    let h = image.height
-
-    if (translate.deg === 90 || translate.deg === 270) {
-      w = image.height
-      h = image.width
-    }
-
-    const canvas = document.createElement('canvas')
-    canvas.width = w
-    canvas.height = h
-
-    const context = canvas.getContext('2d')
-    context.translate(w / 2, h / 2)
-    if (translate.canvas) {
-      context.rotate(translate.rad)
-      context.scale(translate.scaleX, translate.scaleY)
-    }
-    context.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height)
-
-    return canvas
-  }
-
-  /**
-   * Save a <canvas> element's content to a Blob object.
-   *
-   * @param {HTMLCanvasElement} canvas
-   * @returns {Promise}
-   */
-  canvasToBlob (canvas, type, quality) {
-    try {
-      canvas.getContext('2d').getImageData(0, 0, 1, 1)
-    } catch (err) {
-      if (err.code === 18) {
-        return Promise.reject(new Error('cannot read image, probably an svg with external resources'))
-      }
-    }
-
-    if (canvas.toBlob) {
-      return new Promise(resolve => {
-        canvas.toBlob(resolve, type, quality)
-      }).then((blob) => {
-        if (blob === null) {
-          throw new Error('cannot read image, probably an svg with external resources')
-        }
-        return blob
-      })
-    }
-    return Promise.resolve().then(() => {
-      return dataURItoBlob(canvas.toDataURL(type, quality), {})
-    }).then((blob) => {
-      if (blob === null) {
-        throw new Error('could not extract blob, probably an old browser')
-      }
-      return blob
-    })
+    return img
   }
   }
 
 
   /**
   /**
@@ -255,7 +256,7 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
       const current = this.uppy.getFile(this.queue.shift())
       const current = this.uppy.getFile(this.queue.shift())
       if (!current) {
       if (!current) {
         this.uppy.log('[ThumbnailGenerator] file was removed before a thumbnail could be generated, but not removed from the queue. This is probably a bug', 'error')
         this.uppy.log('[ThumbnailGenerator] file was removed before a thumbnail could be generated, but not removed from the queue. This is probably a bug', 'error')
-        return
+        return Promise.resolve()
       }
       }
       return this.requestThumbnail(current)
       return this.requestThumbnail(current)
         .catch(() => {}) // eslint-disable-line node/handle-callback-err
         .catch(() => {}) // eslint-disable-line node/handle-callback-err
@@ -264,6 +265,7 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
     this.queueProcessing = false
     this.queueProcessing = false
     this.uppy.log('[ThumbnailGenerator] Emptied thumbnail queue')
     this.uppy.log('[ThumbnailGenerator] Emptied thumbnail queue')
     this.uppy.emit('thumbnail:all-generated')
     this.uppy.emit('thumbnail:all-generated')
+    return Promise.resolve()
   }
   }
 
 
   requestThumbnail (file) {
   requestThumbnail (file) {
@@ -396,3 +398,8 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
     }
     }
   }
   }
 }
 }
+
+// TODO: remove these methods from the prototype in the next major.
+ThumbnailGenerator.prototype.canvasToBlob = canvasToBlob
+ThumbnailGenerator.prototype.protect = protect
+ThumbnailGenerator.prototype.rotateImage = rotateImage

+ 9 - 6
packages/@uppy/thumbnail-generator/src/index.test.js

@@ -1,6 +1,7 @@
-const { UIPlugin } = require('@uppy/core')
-const emitter = require('namespace-emitter')
-const ThumbnailGeneratorPlugin = require('./index')
+import { describe, it, expect, jest } from '@jest/globals'
+import { UIPlugin } from '@uppy/core'
+import emitter from 'namespace-emitter'
+import ThumbnailGeneratorPlugin from './index.js'
 
 
 const delay = duration => new Promise(resolve => setTimeout(resolve, duration))
 const delay = duration => new Promise(resolve => setTimeout(resolve, duration))
 
 
@@ -139,6 +140,9 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
       URL.revokeObjectURL = jest.fn(() => null)
       URL.revokeObjectURL = jest.fn(() => null)
 
 
       try {
       try {
+        const file1 = { id: 1, name: 'bar.jpg', type: 'image/jpeg', data: new Blob() }
+        const file2 = { id: 2, name: 'bar2.jpg', type: 'image/jpeg', data: new Blob() }
+
         plugin.createThumbnail = jest.fn(async () => {
         plugin.createThumbnail = jest.fn(async () => {
           await delay(50)
           await delay(50)
           return 'blob:http://uppy.io/fake-thumbnail'
           return 'blob:http://uppy.io/fake-thumbnail'
@@ -148,8 +152,6 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
           if (id === 2) file2.preview = preview
           if (id === 2) file2.preview = preview
         })
         })
 
 
-        const file1 = { id: 1, name: 'bar.jpg', type: 'image/jpeg', data: new Blob() }
-        const file2 = { id: 2, name: 'bar2.jpg', type: 'image/jpeg', data: new Blob() }
         core.mockFile(file1.id, file1)
         core.mockFile(file1.id, file1)
         core.emit('file-added', file1)
         core.emit('file-added', file1)
         core.mockFile(file2.id, file2)
         core.mockFile(file2.id, file2)
@@ -191,7 +193,8 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
           expect(file.id).toBe(expected.shift())
           expect(file.id).toBe(expected.shift())
           expect(preview).toBe(`blob:${file.id}.png`)
           expect(preview).toBe(`blob:${file.id}.png`)
         } catch (err) {
         } catch (err) {
-          return reject(err)
+          reject(err)
+          return
         }
         }
         if (expected.length === 0) resolve()
         if (expected.length === 0) resolve()
       })
       })

+ 1 - 1
packages/@uppy/thumbnail-generator/src/locale.js

@@ -1,4 +1,4 @@
-module.exports = {
+export default {
   strings: {
   strings: {
     generatingThumbnails: 'Generating thumbnails...',
     generatingThumbnails: 'Generating thumbnails...',
   },
   },

+ 13 - 0
website/src/docs/thumbnail-generator.md

@@ -58,6 +58,19 @@ uppy.use(ThumbnailGenerator, {
 
 
 A unique identifier for this plugin. It defaults to `'ThumbnailGenerator'`.
 A unique identifier for this plugin. It defaults to `'ThumbnailGenerator'`.
 
 
+### `locale: {}`
+
+<!-- eslint-disable no-restricted-globals, no-multiple-empty-lines -->
+
+```js
+export default {
+  strings: {
+    generatingThumbnails: 'Generating thumbnails...',
+  },
+}
+
+```
+
 ### `thumbnailWidth: 200`
 ### `thumbnailWidth: 200`
 
 
 Width of the resulting thumbnail. Default thumbnail dimension is 200px. Thumbnails are always proportional and not cropped. If width is provided, height is calculated automatically to match ratio.
 Width of the resulting thumbnail. Default thumbnail dimension is 200px. Thumbnails are always proportional and not cropped. If width is provided, height is calculated automatically to match ratio.

+ 1 - 0
yarn.lock

@@ -10149,6 +10149,7 @@ __metadata:
   version: 0.0.0-use.local
   version: 0.0.0-use.local
   resolution: "@uppy/thumbnail-generator@workspace:packages/@uppy/thumbnail-generator"
   resolution: "@uppy/thumbnail-generator@workspace:packages/@uppy/thumbnail-generator"
   dependencies:
   dependencies:
+    "@jest/globals": ^27.4.2
     "@uppy/utils": "workspace:^"
     "@uppy/utils": "workspace:^"
     exifr: ^7.0.0
     exifr: ^7.0.0
     namespace-emitter: 2.0.1
     namespace-emitter: 2.0.1