Преглед изворни кода

Add `croppedCanvasOptions` to image editor `opts` (#3037)

* Add `croppedCanvasOptions` & fix types

* Update website/src/docs/image-editor.md

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>

* Update website/src/docs/image-editor.md

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>

* Update website/src/docs/image-editor.md

Co-authored-by: Artur Paikin <artur@arturpaikin.com>
Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
Merlijn Vos пре 3 година
родитељ
комит
7db4ced76e

+ 73 - 60
packages/@uppy/image-editor/src/Editor.js

@@ -8,11 +8,11 @@ module.exports = class Editor extends Component {
   }
 
   componentDidMount () {
-    this.cropper = new Cropper(
-      this.imgElement,
-      this.props.opts.cropperOptions
-    )
-    if (this.props.opts.actions.granularRotate) {
+    const { opts } = this.props
+
+    this.cropper = new Cropper(this.imgElement, opts.cropperOptions)
+
+    if (opts.actions.granularRotate) {
       this.imgElement.addEventListener('crop', (ev) => {
         const rotationAngle = ev.detail.rotate
         this.setState({
@@ -29,22 +29,63 @@ module.exports = class Editor extends Component {
   }
 
   save = () => {
-    this.cropper.getCroppedCanvas()
+    const { opts, save, currentImage } = this.props
+
+    this.cropper.getCroppedCanvas(opts.cropperOptions.croppedCanvasOptions)
       .toBlob(
-        (blob) => this.props.save(blob),
-        this.props.currentImage.type,
-        this.props.opts.quality
+        (blob) => save(blob),
+        currentImage.type,
+        opts.quality
       )
   }
 
+  granularRotateOnChange = (ev) => {
+    const { rotationAngle, rotationDelta } = this.state
+    const pendingRotationDelta = Number(ev.target.value) - rotationDelta
+    cancelAnimationFrame(this.granularRotateOnInputNextFrame)
+    if (pendingRotationDelta !== 0) {
+      const pendingRotationAngle = rotationAngle + pendingRotationDelta
+      this.granularRotateOnInputNextFrame = requestAnimationFrame(() => {
+        this.cropper.rotateTo(pendingRotationAngle)
+      })
+    }
+  }
+
+  renderGranularRotate () {
+    const { i18n } = this.props
+    const { rotationDelta, rotationAngle } = this.state
+
+    return (
+      // eslint-disable-next-line jsx-a11y/label-has-associated-control
+      <label
+        data-microtip-position="top"
+        role="tooltip"
+        aria-label={`${rotationAngle}º`}
+        className="uppy-ImageCropper-rangeWrapper uppy-u-reset"
+      >
+        <input
+          className="uppy-ImageCropper-range uppy-u-reset"
+          type="range"
+          onInput={this.granularRotateOnChange}
+          onChange={this.granularRotateOnChange}
+          value={rotationDelta}
+          min="-45"
+          max="44"
+          aria-label={i18n('rotate')}
+        />
+      </label>
+    )
+  }
+
   renderRevert () {
+    const { i18n } = this.props
+
     return (
       <button
         type="button"
         className="uppy-u-reset uppy-c-btn"
-        aria-label={this.props.i18n('revert')}
+        aria-label={i18n('revert')}
         data-microtip-position="top"
-        role="tooltip"
         onClick={() => {
           this.cropper.reset()
           this.cropper.setAspectRatio(0)
@@ -59,14 +100,15 @@ module.exports = class Editor extends Component {
   }
 
   renderRotate () {
+    const { i18n } = this.props
+
     return (
       <button
         type="button"
         className="uppy-u-reset uppy-c-btn"
         onClick={() => this.cropper.rotate(-90)}
-        aria-label={this.props.i18n('rotate')}
+        aria-label={i18n('rotate')}
         data-microtip-position="top"
-        role="tooltip"
       >
         <svg aria-hidden="true" className="uppy-c-icon" width="24" height="24" viewBox="0 0 24 24">
           <path d="M0 0h24v24H0V0zm0 0h24v24H0V0z" fill="none" />
@@ -76,48 +118,15 @@ module.exports = class Editor extends Component {
     )
   }
 
-  granularRotateOnChange = (ev) => {
-    const { rotationAngle, rotationDelta } = this.state
-    const pendingRotationDelta = Number(ev.target.value) - rotationDelta
-    cancelAnimationFrame(this.granularRotateOnInputNextFrame)
-    if (pendingRotationDelta !== 0) {
-      const pendingRotationAngle = rotationAngle + pendingRotationDelta
-      this.granularRotateOnInputNextFrame = requestAnimationFrame(() => {
-        this.cropper.rotateTo(pendingRotationAngle)
-      })
-    }
-  }
-
-  renderGranularRotate () {
-    return (
-      <label
-        data-microtip-position="top"
-        role="tooltip"
-        aria-label={`${this.state.rotationAngle}º`}
-        className="uppy-ImageCropper-rangeWrapper uppy-u-reset"
-      >
-        <input
-          className="uppy-ImageCropper-range uppy-u-reset"
-          type="range"
-          onInput={this.granularRotateOnChange}
-          onChange={this.granularRotateOnChange}
-          value={this.state.rotationDelta}
-          min="-45"
-          max="44"
-          aria-label={this.props.i18n('rotate')}
-        />
-      </label>
-    )
-  }
-
   renderFlip () {
+    const { i18n } = this.props
+
     return (
       <button
         type="button"
         className="uppy-u-reset uppy-c-btn"
-        aria-label={this.props.i18n('flipHorizontal')}
+        aria-label={i18n('flipHorizontal')}
         data-microtip-position="top"
-        role="tooltip"
         onClick={() => this.cropper.scaleX(-this.cropper.getData().scaleX || -1)}
       >
         <svg aria-hidden="true" className="uppy-c-icon" width="24" height="24" viewBox="0 0 24 24">
@@ -129,13 +138,14 @@ module.exports = class Editor extends Component {
   }
 
   renderZoomIn () {
+    const { i18n } = this.props
+
     return (
       <button
         type="button"
         className="uppy-u-reset uppy-c-btn"
-        aria-label={this.props.i18n('zoomIn')}
+        aria-label={i18n('zoomIn')}
         data-microtip-position="top"
-        role="tooltip"
         onClick={() => this.cropper.zoom(0.1)}
       >
         <svg aria-hidden="true" className="uppy-c-icon" height="24" viewBox="0 0 24 24" width="24">
@@ -148,13 +158,14 @@ module.exports = class Editor extends Component {
   }
 
   renderZoomOut () {
+    const { i18n } = this.props
+
     return (
       <button
         type="button"
         className="uppy-u-reset uppy-c-btn"
-        aria-label={this.props.i18n('zoomOut')}
+        aria-label={i18n('zoomOut')}
         data-microtip-position="top"
-        role="tooltip"
         onClick={() => this.cropper.zoom(-0.1)}
       >
         <svg aria-hidden="true" className="uppy-c-icon" width="24" height="24" viewBox="0 0 24 24">
@@ -166,13 +177,14 @@ module.exports = class Editor extends Component {
   }
 
   renderCropSquare () {
+    const { i18n } = this.props
+
     return (
       <button
         type="button"
         className="uppy-u-reset uppy-c-btn"
-        aria-label={this.props.i18n('aspectRatioSquare')}
+        aria-label={i18n('aspectRatioSquare')}
         data-microtip-position="top"
-        role="tooltip"
         onClick={() => this.cropper.setAspectRatio(1)}
       >
         <svg aria-hidden="true" className="uppy-c-icon" width="24" height="24" viewBox="0 0 24 24">
@@ -184,13 +196,14 @@ module.exports = class Editor extends Component {
   }
 
   renderCropWidescreen () {
+    const { i18n } = this.props
+
     return (
       <button
         type="button"
         className="uppy-u-reset uppy-c-btn"
-        aria-label={this.props.i18n('aspectRatioLandscape')}
+        aria-label={i18n('aspectRatioLandscape')}
         data-microtip-position="top"
-        role="tooltip"
         onClick={() => this.cropper.setAspectRatio(16 / 9)}
       >
         <svg aria-hidden="true" className="uppy-c-icon" width="24" height="24" viewBox="0 0 24 24">
@@ -202,13 +215,14 @@ module.exports = class Editor extends Component {
   }
 
   renderCropWidescreenVertical () {
+    const { i18n } = this.props
+
     return (
       <button
         type="button"
         className="uppy-u-reset uppy-c-btn"
-        aria-label={this.props.i18n('aspectRatioPortrait')}
+        aria-label={i18n('aspectRatioPortrait')}
         data-microtip-position="top"
-        role="tooltip"
         onClick={() => this.cropper.setAspectRatio(9 / 16)}
       >
         <svg aria-hidden="true" className="uppy-c-icon" width="24" height="24" viewBox="0 0 24 24">
@@ -241,7 +255,6 @@ module.exports = class Editor extends Component {
             className="uppy-u-reset uppy-c-btn"
             aria-label={i18n('save')}
             data-microtip-position="top"
-            role="tooltip"
             onClick={() => this.save()}
           >
             <svg aria-hidden="true" className="uppy-c-icon" width="24" height="24" viewBox="0 0 24 24">

+ 5 - 1
packages/@uppy/image-editor/src/index.js

@@ -4,6 +4,7 @@ const { h } = require('preact')
 const Editor = require('./Editor')
 
 module.exports = class ImageEditor extends UIPlugin {
+  // eslint-disable-next-line global-require
   static VERSION = require('../package.json').version
 
   constructor (uppy, opts) {
@@ -31,6 +32,7 @@ module.exports = class ImageEditor extends UIPlugin {
       background: false,
       autoCropArea: 1,
       responsive: true,
+      croppedCanvasOptions: {},
     }
 
     const defaultActions = {
@@ -65,6 +67,7 @@ module.exports = class ImageEditor extends UIPlugin {
     this.i18nInit()
   }
 
+  // eslint-disable-next-line class-methods-use-this
   canEditFile (file) {
     if (!file.type || file.isRemote) {
       return false
@@ -120,8 +123,9 @@ module.exports = class ImageEditor extends UIPlugin {
 
   render () {
     const { currentImage } = this.getPluginState()
+
     if (currentImage === null || currentImage.isRemote) {
-      return
+      return null
     }
 
     return (

+ 6 - 1
packages/@uppy/image-editor/types/index.d.ts

@@ -1,4 +1,5 @@
 import type { PluginOptions, UIPlugin, PluginTarget } from '@uppy/core'
+import type Cropper from 'cropperjs'
 import ImageEditorLocale from './generatedLocale'
 
 type Actions = {
@@ -13,8 +14,12 @@ type Actions = {
   cropWidescreenVertical: boolean
 }
 
+interface UppyCropperOptions extends Cropper.Options {
+  croppedCanvasOptions: Cropper.GetCroppedCanvasOptions
+}
+
 export interface ImageEditorOptions extends PluginOptions {
-  cropperOptions?: Record<string, unknown>
+  cropperOptions?: UppyCropperOptions
   actions?: Actions
   quality?: number
   target?: PluginTarget

+ 5 - 1
website/src/docs/image-editor.md

@@ -64,6 +64,7 @@ uppy.use(ImageEditor, {
     background: false,
     autoCropArea: 1,
     responsive: true,
+    croppedCanvasOptions: {},
   },
   actions: {
     revert: true,
@@ -89,7 +90,10 @@ Quality of the resulting blob that will be saved in Uppy after editing/cropping.
 
 ### `cropperOptions`
 
-Image Editor is using the excellent [Cropper.js](https://fengyuanchen.github.io/cropperjs/), and if you’d like to fine tune the Cropper.js instance, you can pass options to it.
+Image Editor is using the excellent [Cropper.js](https://fengyuanchen.github.io/cropperjs/).
+`cropperOptions` will be directly passed to `Cropper` and therefor can expect the same values as documented
+in their [README](https://github.com/fengyuanchen/cropperjs/blob/HEAD/README.md#options),
+with the addition of `croppedCanvasOptions`, which will be passed to [`getCroppedCanvas`](https://github.com/fengyuanchen/cropperjs/blob/HEAD/README.md#getcroppedcanvasoptions).
 
 ### `actions`