[BUGFIX] Send payload of image cropping wizard via POST 85/58785/5
authorAndreas Fernandez <a.fernandez@scripting-base.de>
Mon, 29 Oct 2018 22:04:10 +0000 (23:04 +0100)
committerBenni Mack <benni@typo3.org>
Thu, 8 Nov 2018 19:37:02 +0000 (20:37 +0100)
The image cropper wizard configuration can become very large, which
might break the wizard as the configuration is sent via query parameters
and "Request-URI Too Long" might kick in.

The payload is now sent via POST to bypass this issue. As our Modal API
is currently not capable of sending AJAX requests via POST, the logic
regarding the icon spinner is duplicated for the time being.

Resolves: #82225
Releases: master, 8.7
Change-Id: I7106b62fcc09101bc5147277225d1b8e89133d5c
Reviewed-on: https://review.typo3.org/58785
Tested-by: TYPO3com <no-reply@typo3.com>
Tested-by: Jonas Eberle <flightvision@googlemail.com>
Reviewed-by: Andreas Wolf <andreas.wolf@typo3.org>
Reviewed-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Tested-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
typo3/sysext/backend/Classes/Controller/Wizard/ImageManipulationController.php
typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
typo3/sysext/backend/Resources/Private/Templates/ImageManipulation/ImageManipulationElement.html
typo3/sysext/backend/Resources/Private/TypeScript/ImageManipulation.ts
typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js

index 3957977..2fd647e 100644 (file)
@@ -58,8 +58,8 @@ class ImageManipulationController
     public function getWizardContent(ServerRequestInterface $request): ResponseInterface
     {
         if ($this->isSignatureValid($request)) {
-            $queryParams = json_decode($request->getQueryParams()['arguments'], true);
-            $fileUid = $queryParams['image'];
+            $parsedBody = json_decode($request->getParsedBody()['arguments'], true);
+            $fileUid = $parsedBody['image'];
             $image = null;
             if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
                 try {
@@ -69,7 +69,7 @@ class ImageManipulationController
             }
             $viewData = [
                 'image' => $image,
-                'cropVariants' => $queryParams['cropVariants']
+                'cropVariants' => $parsedBody['cropVariants']
             ];
             $content = $this->templateView->renderSection('Main', $viewData);
             return new HtmlResponse($content);
@@ -80,12 +80,12 @@ class ImageManipulationController
     /**
      * Check if hmac signature is correct
      *
-     * @param ServerRequestInterface $request the request with the GET parameters
+     * @param ServerRequestInterface $request the request with the POST parameters
      * @return bool
      */
     protected function isSignatureValid(ServerRequestInterface $request): bool
     {
-        $token = GeneralUtility::hmac($request->getQueryParams()['arguments'], 'ajax_wizard_image_manipulation');
-        return hash_equals($token, $request->getQueryParams()['signature']);
+        $token = GeneralUtility::hmac($request->getParsedBody()['arguments'], 'ajax_wizard_image_manipulation');
+        return hash_equals($token, $request->getParsedBody()['signature']);
     }
 }
index 1581ece..b699985 100644 (file)
@@ -35,6 +35,11 @@ use TYPO3\CMS\Fluid\View\StandaloneView;
 class ImageManipulationElement extends AbstractFormElement
 {
     /**
+     * @var string
+     */
+    private $wizardRouteName = 'ajax_wizard_image_manipulation';
+
+    /**
      * Default element configuration
      *
      * @var array
@@ -183,7 +188,8 @@ class ImageManipulationElement extends AbstractFormElement
                 'validation' => '[]'
             ],
             'config' => $config,
-            'wizardUri' => $this->getWizardUri($config['cropVariants'], $file),
+            'wizardUri' => $this->getWizardUri(),
+            'wizardPayload' => json_encode($this->getWizardPayload($config['cropVariants'], $file)),
             'previewUrl' => $this->getPreviewUrl($this->data['databaseRow'], $file),
         ];
 
@@ -308,19 +314,27 @@ class ImageManipulationElement extends AbstractFormElement
     }
 
     /**
+     * @return string
+     */
+    protected function getWizardUri(): string
+    {
+        return (string)$this->uriBuilder->buildUriFromRoute($this->wizardRouteName);
+    }
+
+    /**
      * @param array $cropVariants
      * @param File $image
-     * @return string
+     * @return array
      */
-    protected function getWizardUri(array $cropVariants, File $image): string
+    protected function getWizardPayload(array $cropVariants, File $image): array
     {
-        $routeName = 'ajax_wizard_image_manipulation';
         $arguments = [
             'cropVariants' => $cropVariants,
             'image' => $image->getUid(),
         ];
         $uriArguments['arguments'] = json_encode($arguments);
-        $uriArguments['signature'] = GeneralUtility::hmac($uriArguments['arguments'], $routeName);
-        return (string)$this->uriBuilder->buildUriFromRoute($routeName, $uriArguments);
+        $uriArguments['signature'] = GeneralUtility::hmac($uriArguments['arguments'], $this->wizardRouteName);
+
+        return $uriArguments;
     }
 }
index 3a6c69d..ea4c51b 100644 (file)
@@ -14,6 +14,7 @@
                                                                        data-formengine-validation-rules="{formEngine.validation}"/>
                                                        <button class="btn btn-default t3js-image-manipulation-trigger"
                                                                        data-url="{wizardUri}"
+                                                                       data-payload="{wizardPayload}"
                                                                        data-preview-url="{previewUrl}"
                                                                        data-severity="notice"
                                                                        data-modal-title="{f:render(partial: 'ModalTitle', section:'Main', arguments: _all)}"
index c4b26a4..0835d94 100644 (file)
@@ -14,6 +14,7 @@
 import * as $ from 'jquery';
 import 'jquery-ui/draggable';
 import 'jquery-ui/resizable';
+import Icons = require('./Icons');
 import Modal = require('./Modal');
 import ImagesLoaded = require('TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min');
 
@@ -224,55 +225,62 @@ class ImageManipulation {
     const buttonDismissText: string = this.trigger.data('buttonDismissText');
     const buttonSaveText: string = this.trigger.data('buttonSaveText');
     const imageUri: string = this.trigger.data('url');
+    const payload: object = this.trigger.data('payload');
     const initCropperModal: ()  => void = this.initializeCropperModal.bind(this);
 
-    /**
-     * Open modal with image to crop
-     */
-    this.currentModal = Modal.advanced({
-      additionalCssClasses: ['modal-image-manipulation'],
-      ajaxCallback: initCropperModal,
-      buttons: [
-        {
-          btnClass: 'btn-default pull-left',
-          dataAttributes: {
-            method: 'preview',
+    Icons.getIcon('spinner-circle', Icons.sizes.default, null, null, Icons.markupIdentifiers.inline).done((icon: string): void => {
+      /**
+       * Open modal with image to crop
+       */
+      this.currentModal = Modal.advanced({
+        additionalCssClasses: ['modal-image-manipulation'],
+        buttons: [
+          {
+            btnClass: 'btn-default pull-left',
+            dataAttributes: {
+              method: 'preview',
+            },
+            icon: 'actions-view',
+            text: buttonPreviewText,
           },
-          icon: 'actions-view',
-          text: buttonPreviewText,
-        },
-        {
-          btnClass: 'btn-default',
-          dataAttributes: {
-            method: 'dismiss',
+          {
+            btnClass: 'btn-default',
+            dataAttributes: {
+              method: 'dismiss',
+            },
+            icon: 'actions-close',
+            text: buttonDismissText,
           },
-          icon: 'actions-close',
-          text: buttonDismissText,
-        },
-        {
-          btnClass: 'btn-primary',
-          dataAttributes: {
-            method: 'save',
+          {
+            btnClass: 'btn-primary',
+            dataAttributes: {
+              method: 'save',
+            },
+            icon: 'actions-document-save',
+            text: buttonSaveText,
           },
-          icon: 'actions-document-save',
-          text: buttonSaveText,
+        ],
+        callback: (currentModal: JQuery): void => {
+          $.post({
+            url: imageUri,
+            data: payload
+          }).done((response: string): void => {
+            initCropperModal();
+            currentModal.find('.t3js-modal-body').append(response).addClass('cropper');
+          });
         },
-      ],
-      callback: (currentModal: JQuery): void => {
-        currentModal.find('.t3js-modal-body')
-                    .addClass('cropper');
-      },
-      content: imageUri,
-      size: Modal.sizes.full,
-      style: Modal.styles.dark,
-      title: modalTitle,
-      type: 'ajax',
-    });
-    this.currentModal.on('hide.bs.modal', (e: JQueryEventObject): void => {
-      this.destroy();
+        content: '<div class="modal-loading">' + icon + '</div>',
+        size: Modal.sizes.full,
+        style: Modal.styles.dark,
+        title: modalTitle,
+      });
+
+      this.currentModal.on('hide.bs.modal', (e: JQueryEventObject): void => {
+        this.destroy();
+      });
+      // do not dismiss the modal when clicking beside it to avoid data loss
+      this.currentModal.data('bs.modal').options.backdrop = 'static';
     });
-    // do not dismiss the modal when clicking beside it to avoid data loss
-    this.currentModal.data('bs.modal').options.backdrop = 'static';
   }
 
   /**
@@ -938,7 +946,9 @@ class ImageManipulation {
    */
   private destroy(): void {
     if (this.currentModal) {
-      this.cropper.cropper('destroy');
+      if (typeof this.cropper !== 'undefined' && this.cropper !== null) {
+        this.cropper.cropper('destroy');
+      }
       this.cropper = null;
       this.currentModal = null;
       this.data = null;
index 15ed626..48fc8e1 100644 (file)
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports","jquery","./Modal","TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min","jquery-ui/draggable","jquery-ui/resizable"],function(t,r,e,a,i){"use strict";return new(function(){function t(){var r=this;this.cropImageContainerSelector="#t3js-crop-image-container",this.cropImageSelector="#t3js-crop-image",this.coverAreaSelector=".t3js-cropper-cover-area",this.cropInfoSelector=".t3js-cropper-info-crop",this.focusAreaSelector="#t3js-cropper-focus-area",this.defaultFocusArea={height:1/3,width:1/3,x:0,y:0},this.defaultOpts={autoCrop:!0,autoCropArea:"0.7",dragMode:"crop",guides:!0,responsive:!0,viewMode:1,zoomable:!1},this.resizeTimeout=450,this.cropBuiltHandler=function(){var a=r.cropper.cropper("getImageData"),i=r.currentModal.find(r.cropImageSelector);r.imageOriginalSizeFactor=i.data("originalWidth")/a.naturalWidth,r.cropVariantTriggers.each(function(t,i){var o=e(i).attr("data-crop-variant-id"),n=r.convertRelativeToAbsoluteCropArea(r.data[o].cropArea,a),s=e.extend(!0,{},r.data[o],{cropArea:n});r.updatePreviewThumbnail(s,e(i))}),r.currentCropVariant.cropArea=r.convertRelativeToAbsoluteCropArea(r.currentCropVariant.cropArea,a),r.cropBox=r.currentModal.find(".cropper-crop-box"),r.setCropArea(r.currentCropVariant.cropArea),r.currentCropVariant.coverAreas&&r.initCoverAreas(r.cropBox,r.currentCropVariant.coverAreas),r.currentCropVariant.focusArea&&(t.isEmptyArea(r.currentCropVariant.focusArea)&&(r.currentCropVariant.focusArea=e.extend(!0,{},r.defaultFocusArea)),r.initFocusArea(r.cropBox),r.scaleAndMoveFocusArea(r.currentCropVariant.focusArea)),r.currentCropVariant.selectedRatio&&(r.setAspectRatio(r.currentCropVariant.allowedAspectRatios[r.currentCropVariant.selectedRatio]),r.setCropArea(r.currentCropVariant.cropArea),r.currentModal.find("[data-option='"+r.currentCropVariant.selectedRatio+"']").addClass("active")),r.cropperCanvas.addClass("is-visible")},this.cropMoveHandler=function(t){r.currentCropVariant.cropArea=e.extend(!0,r.currentCropVariant.cropArea,{height:Math.floor(t.height),width:Math.floor(t.width),x:Math.floor(t.x),y:Math.floor(t.y)}),r.updatePreviewThumbnail(r.currentCropVariant,r.activeCropVariantTrigger),r.updateCropVariantData(r.currentCropVariant);var a=Math.round(r.currentCropVariant.cropArea.width*r.imageOriginalSizeFactor),i=Math.round(r.currentCropVariant.cropArea.height*r.imageOriginalSizeFactor);r.cropInfo.text(a+"×"+i+" px")},this.cropStartHandler=function(){r.currentCropVariant.focusArea&&(r.focusArea.draggable("option","disabled",!0),r.focusArea.resizable("option","disabled",!0))},this.cropEndHandler=function(){r.currentCropVariant.focusArea&&(r.focusArea.draggable("option","disabled",!1),r.focusArea.resizable("option","disabled",!1))},e(window).resize(function(){r.cropper&&r.cropper.cropper("destroy")}),this.resizeEnd(function(){r.cropper&&r.init()})}return t.isEmptyArea=function(t){return e.isEmptyObject(t)},t.wait=function(t,r){window.setTimeout(t,r)},t.toCssPercent=function(t){return 100*t+"%"},t.serializeCropVariants=function(t){return JSON.stringify(t,function(t,r){return"id"===t||"title"===t||"allowedAspectRatios"===t||"coverAreas"===t?void 0:r})},t.prototype.initializeTrigger=function(){var t=this;e(".t3js-image-manipulation-trigger").off("click").click(function(r){r.preventDefault(),t.trigger=e(r.currentTarget),t.show()})},t.prototype.initializeCropperModal=function(){var t=this,r=this.currentModal.find(this.cropImageSelector);i(r,function(){setTimeout(function(){t.init()},100)})},t.prototype.show=function(){var t=this,r=this.trigger.data("modalTitle"),e=this.trigger.data("buttonPreviewText"),i=this.trigger.data("buttonDismissText"),o=this.trigger.data("buttonSaveText"),n=this.trigger.data("url"),s=this.initializeCropperModal.bind(this);this.currentModal=a.advanced({additionalCssClasses:["modal-image-manipulation"],ajaxCallback:s,buttons:[{btnClass:"btn-default pull-left",dataAttributes:{method:"preview"},icon:"actions-view",text:e},{btnClass:"btn-default",dataAttributes:{method:"dismiss"},icon:"actions-close",text:i},{btnClass:"btn-primary",dataAttributes:{method:"save"},icon:"actions-document-save",text:o}],callback:function(t){t.find(".t3js-modal-body").addClass("cropper")},content:n,size:a.sizes.full,style:a.styles.dark,title:r,type:"ajax"}),this.currentModal.on("hide.bs.modal",function(r){t.destroy()}),this.currentModal.data("bs.modal").options.backdrop="static"},t.prototype.init=function(){var r=this,a=this.currentModal.find(this.cropImageSelector),i=e(a).height(),o=e(a).width(),n=this.trigger.attr("data-crop-variants");if(!n)throw new TypeError("ImageManipulation: No cropVariants data found for image");this.data=e.isEmptyObject(this.data)?JSON.parse(n):this.data,this.currentModal.find(this.cropImageContainerSelector).css({height:i,width:o}),this.cropVariantTriggers=this.currentModal.find(".t3js-crop-variant-trigger"),this.activeCropVariantTrigger=this.currentModal.find(".t3js-crop-variant-trigger.is-active"),this.cropInfo=this.currentModal.find(this.cropInfoSelector),this.saveButton=this.currentModal.find("[data-method=save]"),this.previewButton=this.currentModal.find("[data-method=preview]"),this.dismissButton=this.currentModal.find("[data-method=dismiss]"),this.resetButton=this.currentModal.find("[data-method=reset]"),this.cropperCanvas=this.currentModal.find("#js-crop-canvas"),this.aspectRatioTrigger=this.currentModal.find("[data-method=setAspectRatio]"),this.currentCropVariant=this.data[this.activeCropVariantTrigger.attr("data-crop-variant-id")],this.cropVariantTriggers.off("click").on("click",function(t){if(e(t.currentTarget).hasClass("is-active"))return t.stopPropagation(),void t.preventDefault();r.activeCropVariantTrigger.removeClass("is-active"),e(t.currentTarget).addClass("is-active"),r.activeCropVariantTrigger=e(t.currentTarget);var a=r.data[r.activeCropVariantTrigger.attr("data-crop-variant-id")],i=r.cropper.cropper("getImageData");a.cropArea=r.convertRelativeToAbsoluteCropArea(a.cropArea,i),r.currentCropVariant=e.extend(!0,{},a),r.update(a)}),this.aspectRatioTrigger.off("click").on("click",function(t){var a=e(t.currentTarget).attr("data-option"),i=e.extend(!0,{},r.currentCropVariant),o=i.allowedAspectRatios[a];r.setAspectRatio(o),r.setCropArea(i.cropArea),r.currentCropVariant=e.extend(!0,{},i,{selectedRatio:a}),r.update(r.currentCropVariant)}),this.saveButton.off("click").on("click",function(){r.save(r.data)}),this.trigger.attr("data-preview-url")?this.previewButton.off("click").on("click",function(){r.openPreview(r.data)}):this.previewButton.hide(),this.dismissButton.off("click").on("click",function(){r.currentModal.modal("hide")}),this.resetButton.off("click").on("click",function(t){var a=r.cropper.cropper("getImageData"),i=e(t.currentTarget).attr("data-crop-variant");if(t.preventDefault(),t.stopPropagation(),!i)throw new TypeError("TYPO3 Cropper: No cropVariant data attribute found on reset element.");var o=JSON.parse(i),n=r.convertRelativeToAbsoluteCropArea(o.cropArea,a);r.currentCropVariant=e.extend(!0,{},o,{cropArea:n}),r.update(r.currentCropVariant)}),t.isEmptyArea(this.currentCropVariant.cropArea)&&(this.defaultOpts=e.extend({autoCropArea:1},this.defaultOpts)),this.cropper=top.$(a).cropper(e.extend(this.defaultOpts,{built:this.cropBuiltHandler,crop:this.cropMoveHandler,cropend:this.cropEndHandler,cropstart:this.cropStartHandler,data:this.currentCropVariant.cropArea}))},t.prototype.update=function(r){var a=e.extend(!0,{},r),i=r.allowedAspectRatios[r.selectedRatio];this.currentModal.find("[data-option]").removeClass("active"),this.currentModal.find('[data-option="'+r.selectedRatio+'"]').addClass("active"),this.setAspectRatio(i),this.setCropArea(a.cropArea),this.currentCropVariant=e.extend(!0,{},a,r),this.cropBox.find(this.coverAreaSelector).remove(),this.cropBox.has(this.focusAreaSelector).length&&(this.focusArea.resizable("destroy").draggable("destroy"),this.focusArea.remove()),r.focusArea&&(t.isEmptyArea(r.focusArea)&&(this.currentCropVariant.focusArea=e.extend(!0,{},this.defaultFocusArea)),this.initFocusArea(this.cropBox),this.scaleAndMoveFocusArea(this.currentCropVariant.focusArea)),r.coverAreas&&this.initCoverAreas(this.cropBox,this.currentCropVariant.coverAreas),this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger)},t.prototype.initFocusArea=function(r){var a=this;this.focusArea=e('<div id="t3js-cropper-focus-area" class="cropper-focus-area"></div>'),r.append(this.focusArea),this.focusArea.draggable({containment:r,create:function(){a.scaleAndMoveFocusArea(a.currentCropVariant.focusArea)},drag:function(){var t=r.offset(),e=t.left,i=t.top,o=a.focusArea.offset(),n=o.left,s=o.top,c=a.currentCropVariant,p=c.focusArea,u=c.coverAreas;p.x=(n-e)/r.width(),p.y=(s-i)/r.height(),a.updatePreviewThumbnail(a.currentCropVariant,a.activeCropVariantTrigger),a.checkFocusAndCoverAreasCollision(p,u)?a.focusArea.addClass("has-nodrop"):a.focusArea.removeClass("has-nodrop")},revert:function(){var e=r.offset(),i=e.left,o=e.top,n=a.focusArea.offset(),s=n.left,c=n.top,p=a.currentCropVariant,u=p.focusArea,d=p.coverAreas;return!!a.checkFocusAndCoverAreasCollision(u,d)&&(a.focusArea.removeClass("has-nodrop"),t.wait(function(){u.x=(s-i)/r.width(),u.y=(c-o)/r.height(),a.updateCropVariantData(a.currentCropVariant)},250),!0)},revertDuration:200,stop:function(){var t=r.offset(),e=t.left,i=t.top,o=a.focusArea.offset(),n=o.left,s=o.top,c=a.currentCropVariant.focusArea;c.x=(n-e)/r.width(),c.y=(s-i)/r.height(),a.scaleAndMoveFocusArea(c)}}).resizable({containment:r,handles:"all",resize:function(){var t=r.offset(),e=t.left,i=t.top,o=a.focusArea.offset(),n=o.left,s=o.top,c=a.currentCropVariant,p=c.focusArea,u=c.coverAreas;p.height=a.focusArea.height()/r.height(),p.width=a.focusArea.width()/r.width(),p.x=(n-e)/r.width(),p.y=(s-i)/r.height(),a.updatePreviewThumbnail(a.currentCropVariant,a.activeCropVariantTrigger),a.checkFocusAndCoverAreasCollision(p,u)?a.focusArea.addClass("has-nodrop"):a.focusArea.removeClass("has-nodrop")},stop:function(t,i){var o=r.offset(),n=o.left,s=o.top,c=a.focusArea.offset(),p=c.left,u=c.top,d=a.currentCropVariant,h=d.focusArea,l=d.coverAreas;a.checkFocusAndCoverAreasCollision(h,l)?i.element.animate(e.extend(i.originalPosition,i.originalSize),250,function(){h.height=a.focusArea.height()/r.height(),h.height=a.focusArea.height()/r.height(),h.width=a.focusArea.width()/r.width(),h.x=(p-n)/r.width(),h.y=(u-s)/r.height(),a.scaleAndMoveFocusArea(h),a.focusArea.removeClass("has-nodrop")}):a.scaleAndMoveFocusArea(h)}})},t.prototype.initCoverAreas=function(r,a){a.forEach(function(a){var i=e('<div class="cropper-cover-area t3js-cropper-cover-area"></div>');r.append(i),i.css({height:t.toCssPercent(a.height),left:t.toCssPercent(a.x),top:t.toCssPercent(a.y),width:t.toCssPercent(a.width)})})},t.prototype.updatePreviewThumbnail=function(r,e){var a,i=e.find(".t3js-cropper-preview-thumbnail-crop-area"),o=e.find(".t3js-cropper-preview-thumbnail-crop-image"),n=e.find(".t3js-cropper-preview-thumbnail-focus-area"),s=this.cropper.cropper("getImageData");i.css({height:t.toCssPercent(r.cropArea.height/s.naturalHeight),left:t.toCssPercent(r.cropArea.x/s.naturalWidth),top:t.toCssPercent(r.cropArea.y/s.naturalHeight),width:t.toCssPercent(r.cropArea.width/s.naturalWidth)}),r.focusArea&&n.css({height:t.toCssPercent(r.focusArea.height),left:t.toCssPercent(r.focusArea.x),top:t.toCssPercent(r.focusArea.y),width:t.toCssPercent(r.focusArea.width)}),a=i.css(["width","height","left","top"]),o.css({height:parseFloat(a.height)*(1/(r.cropArea.height/s.naturalHeight))+"px",margin:-1*parseFloat(a.left)+"px",marginTop:-1*parseFloat(a.top)+"px",width:parseFloat(a.width)*(1/(r.cropArea.width/s.naturalWidth))+"px"})},t.prototype.scaleAndMoveFocusArea=function(r){this.focusArea.css({height:t.toCssPercent(r.height),left:t.toCssPercent(r.x),top:t.toCssPercent(r.y),width:t.toCssPercent(r.width)}),this.currentCropVariant.focusArea=r,this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger),this.updateCropVariantData(this.currentCropVariant)},t.prototype.updateCropVariantData=function(t){var r=this.cropper.cropper("getImageData"),a=this.convertAbsoluteToRelativeCropArea(t.cropArea,r);this.data[t.id]=e.extend(!0,{},t,{cropArea:a})},t.prototype.setAspectRatio=function(t){this.cropper.cropper("setAspectRatio",t.value)},t.prototype.setCropArea=function(t){0===this.currentCropVariant.allowedAspectRatios[this.currentCropVariant.selectedRatio].value?this.cropper.cropper("setData",{height:t.height,width:t.width,x:t.x,y:t.y}):this.cropper.cropper("setData",{height:t.height,x:t.x,y:t.y})},t.prototype.checkFocusAndCoverAreasCollision=function(t,r){return!!r&&r.some(function(r){return t.x<r.x+r.width&&t.x+t.width>r.x&&t.y<r.y+r.height&&t.height+t.y>r.y})},t.prototype.convertAbsoluteToRelativeCropArea=function(t,r){var e=t.height,a=t.width,i=t.x,o=t.y;return{height:e/r.naturalHeight,width:a/r.naturalWidth,x:i/r.naturalWidth,y:o/r.naturalHeight}},t.prototype.convertRelativeToAbsoluteCropArea=function(t,r){var e=t.height,a=t.width,i=t.x,o=t.y;return{height:e*r.naturalHeight,width:a*r.naturalWidth,x:i*r.naturalWidth,y:o*r.naturalHeight}},t.prototype.setPreviewImages=function(t){var r=this,a=this.cropper,i=a.cropper("getImageData");Object.keys(t).forEach(function(o){var n=t[o],s=r.convertRelativeToAbsoluteCropArea(n.cropArea,i),c=r.trigger.closest(".form-group").find('.t3js-image-manipulation-preview[data-crop-variant-id="'+o+'"]'),p=r.trigger.closest(".form-group").find('.t3js-image-manipulation-selected-ratio[data-crop-variant-id="'+o+'"]');if(0!==c.length){var u=c.width(),d=c.data("preview-height"),h=s.width/s.height,l=u/h;l>d?u=d*h:d=l,u>s.width&&(u=s.width,d=s.height);var f=u/s.width,g=e("<div />").html('<img src="'+a.attr("src")+'">'),v=r.currentModal.find('.t3-js-ratio-title[data-ratio-id="'+n.id+n.selectedRatio+'"]');p.text(v.text()),g.addClass("cropper-preview-container"),c.empty().append(g),g.wrap('<span class="thumbnail thumbnail-status"></span>'),g.width(u).height(d).find("img").css({height:i.naturalHeight*f,left:-s.x*f,top:-s.y*f,width:i.naturalWidth*f})}})},t.prototype.openPreview=function(r){var e=t.serializeCropVariants(r),a=this.trigger.attr("data-preview-url");a=a+"&cropVariants="+encodeURIComponent(e),window.open(a,"TYPO3ImageManipulationPreview")},t.prototype.save=function(r){var a=t.serializeCropVariants(r),i=e("#"+this.trigger.attr("data-field"));this.trigger.attr("data-crop-variants",JSON.stringify(r)),this.setPreviewImages(r),i.val(a),this.currentModal.modal("hide")},t.prototype.destroy=function(){this.currentModal&&(this.cropper.cropper("destroy"),this.cropper=null,this.currentModal=null,this.data=null)},t.prototype.resizeEnd=function(t){var r,a=this;e(window).on("resize",function(){clearTimeout(r),r=setTimeout(function(){t()},a.resizeTimeout)})},t}())});
\ No newline at end of file
+define(["require","exports","jquery","./Icons","./Modal","TYPO3/CMS/Core/Contrib/imagesloaded.pkgd.min","jquery-ui/draggable","jquery-ui/resizable"],function(t,r,e,a,i,o){"use strict";return new(function(){function t(){var r=this;this.cropImageContainerSelector="#t3js-crop-image-container",this.cropImageSelector="#t3js-crop-image",this.coverAreaSelector=".t3js-cropper-cover-area",this.cropInfoSelector=".t3js-cropper-info-crop",this.focusAreaSelector="#t3js-cropper-focus-area",this.defaultFocusArea={height:1/3,width:1/3,x:0,y:0},this.defaultOpts={autoCrop:!0,autoCropArea:"0.7",dragMode:"crop",guides:!0,responsive:!0,viewMode:1,zoomable:!1},this.resizeTimeout=450,this.cropBuiltHandler=function(){var a=r.cropper.cropper("getImageData"),i=r.currentModal.find(r.cropImageSelector);r.imageOriginalSizeFactor=i.data("originalWidth")/a.naturalWidth,r.cropVariantTriggers.each(function(t,i){var o=e(i).attr("data-crop-variant-id"),n=r.convertRelativeToAbsoluteCropArea(r.data[o].cropArea,a),s=e.extend(!0,{},r.data[o],{cropArea:n});r.updatePreviewThumbnail(s,e(i))}),r.currentCropVariant.cropArea=r.convertRelativeToAbsoluteCropArea(r.currentCropVariant.cropArea,a),r.cropBox=r.currentModal.find(".cropper-crop-box"),r.setCropArea(r.currentCropVariant.cropArea),r.currentCropVariant.coverAreas&&r.initCoverAreas(r.cropBox,r.currentCropVariant.coverAreas),r.currentCropVariant.focusArea&&(t.isEmptyArea(r.currentCropVariant.focusArea)&&(r.currentCropVariant.focusArea=e.extend(!0,{},r.defaultFocusArea)),r.initFocusArea(r.cropBox),r.scaleAndMoveFocusArea(r.currentCropVariant.focusArea)),r.currentCropVariant.selectedRatio&&(r.setAspectRatio(r.currentCropVariant.allowedAspectRatios[r.currentCropVariant.selectedRatio]),r.setCropArea(r.currentCropVariant.cropArea),r.currentModal.find("[data-option='"+r.currentCropVariant.selectedRatio+"']").addClass("active")),r.cropperCanvas.addClass("is-visible")},this.cropMoveHandler=function(t){r.currentCropVariant.cropArea=e.extend(!0,r.currentCropVariant.cropArea,{height:Math.floor(t.height),width:Math.floor(t.width),x:Math.floor(t.x),y:Math.floor(t.y)}),r.updatePreviewThumbnail(r.currentCropVariant,r.activeCropVariantTrigger),r.updateCropVariantData(r.currentCropVariant);var a=Math.round(r.currentCropVariant.cropArea.width*r.imageOriginalSizeFactor),i=Math.round(r.currentCropVariant.cropArea.height*r.imageOriginalSizeFactor);r.cropInfo.text(a+"×"+i+" px")},this.cropStartHandler=function(){r.currentCropVariant.focusArea&&(r.focusArea.draggable("option","disabled",!0),r.focusArea.resizable("option","disabled",!0))},this.cropEndHandler=function(){r.currentCropVariant.focusArea&&(r.focusArea.draggable("option","disabled",!1),r.focusArea.resizable("option","disabled",!1))},e(window).resize(function(){r.cropper&&r.cropper.cropper("destroy")}),this.resizeEnd(function(){r.cropper&&r.init()})}return t.isEmptyArea=function(t){return e.isEmptyObject(t)},t.wait=function(t,r){window.setTimeout(t,r)},t.toCssPercent=function(t){return 100*t+"%"},t.serializeCropVariants=function(t){return JSON.stringify(t,function(t,r){return"id"===t||"title"===t||"allowedAspectRatios"===t||"coverAreas"===t?void 0:r})},t.prototype.initializeTrigger=function(){var t=this;e(".t3js-image-manipulation-trigger").off("click").click(function(r){r.preventDefault(),t.trigger=e(r.currentTarget),t.show()})},t.prototype.initializeCropperModal=function(){var t=this,r=this.currentModal.find(this.cropImageSelector);o(r,function(){setTimeout(function(){t.init()},100)})},t.prototype.show=function(){var t=this,r=this.trigger.data("modalTitle"),o=this.trigger.data("buttonPreviewText"),n=this.trigger.data("buttonDismissText"),s=this.trigger.data("buttonSaveText"),c=this.trigger.data("url"),p=this.trigger.data("payload"),u=this.initializeCropperModal.bind(this);a.getIcon("spinner-circle",a.sizes.default,null,null,a.markupIdentifiers.inline).done(function(a){t.currentModal=i.advanced({additionalCssClasses:["modal-image-manipulation"],buttons:[{btnClass:"btn-default pull-left",dataAttributes:{method:"preview"},icon:"actions-view",text:o},{btnClass:"btn-default",dataAttributes:{method:"dismiss"},icon:"actions-close",text:n},{btnClass:"btn-primary",dataAttributes:{method:"save"},icon:"actions-document-save",text:s}],callback:function(t){e.post({url:c,data:p}).done(function(r){u(),t.find(".t3js-modal-body").append(r).addClass("cropper")})},content:'<div class="modal-loading">'+a+"</div>",size:i.sizes.full,style:i.styles.dark,title:r}),t.currentModal.on("hide.bs.modal",function(r){t.destroy()}),t.currentModal.data("bs.modal").options.backdrop="static"})},t.prototype.init=function(){var r=this,a=this.currentModal.find(this.cropImageSelector),i=e(a).height(),o=e(a).width(),n=this.trigger.attr("data-crop-variants");if(!n)throw new TypeError("ImageManipulation: No cropVariants data found for image");this.data=e.isEmptyObject(this.data)?JSON.parse(n):this.data,this.currentModal.find(this.cropImageContainerSelector).css({height:i,width:o}),this.cropVariantTriggers=this.currentModal.find(".t3js-crop-variant-trigger"),this.activeCropVariantTrigger=this.currentModal.find(".t3js-crop-variant-trigger.is-active"),this.cropInfo=this.currentModal.find(this.cropInfoSelector),this.saveButton=this.currentModal.find("[data-method=save]"),this.previewButton=this.currentModal.find("[data-method=preview]"),this.dismissButton=this.currentModal.find("[data-method=dismiss]"),this.resetButton=this.currentModal.find("[data-method=reset]"),this.cropperCanvas=this.currentModal.find("#js-crop-canvas"),this.aspectRatioTrigger=this.currentModal.find("[data-method=setAspectRatio]"),this.currentCropVariant=this.data[this.activeCropVariantTrigger.attr("data-crop-variant-id")],this.cropVariantTriggers.off("click").on("click",function(t){if(e(t.currentTarget).hasClass("is-active"))return t.stopPropagation(),void t.preventDefault();r.activeCropVariantTrigger.removeClass("is-active"),e(t.currentTarget).addClass("is-active"),r.activeCropVariantTrigger=e(t.currentTarget);var a=r.data[r.activeCropVariantTrigger.attr("data-crop-variant-id")],i=r.cropper.cropper("getImageData");a.cropArea=r.convertRelativeToAbsoluteCropArea(a.cropArea,i),r.currentCropVariant=e.extend(!0,{},a),r.update(a)}),this.aspectRatioTrigger.off("click").on("click",function(t){var a=e(t.currentTarget).attr("data-option"),i=e.extend(!0,{},r.currentCropVariant),o=i.allowedAspectRatios[a];r.setAspectRatio(o),r.setCropArea(i.cropArea),r.currentCropVariant=e.extend(!0,{},i,{selectedRatio:a}),r.update(r.currentCropVariant)}),this.saveButton.off("click").on("click",function(){r.save(r.data)}),this.trigger.attr("data-preview-url")?this.previewButton.off("click").on("click",function(){r.openPreview(r.data)}):this.previewButton.hide(),this.dismissButton.off("click").on("click",function(){r.currentModal.modal("hide")}),this.resetButton.off("click").on("click",function(t){var a=r.cropper.cropper("getImageData"),i=e(t.currentTarget).attr("data-crop-variant");if(t.preventDefault(),t.stopPropagation(),!i)throw new TypeError("TYPO3 Cropper: No cropVariant data attribute found on reset element.");var o=JSON.parse(i),n=r.convertRelativeToAbsoluteCropArea(o.cropArea,a);r.currentCropVariant=e.extend(!0,{},o,{cropArea:n}),r.update(r.currentCropVariant)}),t.isEmptyArea(this.currentCropVariant.cropArea)&&(this.defaultOpts=e.extend({autoCropArea:1},this.defaultOpts)),this.cropper=top.$(a).cropper(e.extend(this.defaultOpts,{built:this.cropBuiltHandler,crop:this.cropMoveHandler,cropend:this.cropEndHandler,cropstart:this.cropStartHandler,data:this.currentCropVariant.cropArea}))},t.prototype.update=function(r){var a=e.extend(!0,{},r),i=r.allowedAspectRatios[r.selectedRatio];this.currentModal.find("[data-option]").removeClass("active"),this.currentModal.find('[data-option="'+r.selectedRatio+'"]').addClass("active"),this.setAspectRatio(i),this.setCropArea(a.cropArea),this.currentCropVariant=e.extend(!0,{},a,r),this.cropBox.find(this.coverAreaSelector).remove(),this.cropBox.has(this.focusAreaSelector).length&&(this.focusArea.resizable("destroy").draggable("destroy"),this.focusArea.remove()),r.focusArea&&(t.isEmptyArea(r.focusArea)&&(this.currentCropVariant.focusArea=e.extend(!0,{},this.defaultFocusArea)),this.initFocusArea(this.cropBox),this.scaleAndMoveFocusArea(this.currentCropVariant.focusArea)),r.coverAreas&&this.initCoverAreas(this.cropBox,this.currentCropVariant.coverAreas),this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger)},t.prototype.initFocusArea=function(r){var a=this;this.focusArea=e('<div id="t3js-cropper-focus-area" class="cropper-focus-area"></div>'),r.append(this.focusArea),this.focusArea.draggable({containment:r,create:function(){a.scaleAndMoveFocusArea(a.currentCropVariant.focusArea)},drag:function(){var t=r.offset(),e=t.left,i=t.top,o=a.focusArea.offset(),n=o.left,s=o.top,c=a.currentCropVariant,p=c.focusArea,u=c.coverAreas;p.x=(n-e)/r.width(),p.y=(s-i)/r.height(),a.updatePreviewThumbnail(a.currentCropVariant,a.activeCropVariantTrigger),a.checkFocusAndCoverAreasCollision(p,u)?a.focusArea.addClass("has-nodrop"):a.focusArea.removeClass("has-nodrop")},revert:function(){var e=r.offset(),i=e.left,o=e.top,n=a.focusArea.offset(),s=n.left,c=n.top,p=a.currentCropVariant,u=p.focusArea,d=p.coverAreas;return!!a.checkFocusAndCoverAreasCollision(u,d)&&(a.focusArea.removeClass("has-nodrop"),t.wait(function(){u.x=(s-i)/r.width(),u.y=(c-o)/r.height(),a.updateCropVariantData(a.currentCropVariant)},250),!0)},revertDuration:200,stop:function(){var t=r.offset(),e=t.left,i=t.top,o=a.focusArea.offset(),n=o.left,s=o.top,c=a.currentCropVariant.focusArea;c.x=(n-e)/r.width(),c.y=(s-i)/r.height(),a.scaleAndMoveFocusArea(c)}}).resizable({containment:r,handles:"all",resize:function(){var t=r.offset(),e=t.left,i=t.top,o=a.focusArea.offset(),n=o.left,s=o.top,c=a.currentCropVariant,p=c.focusArea,u=c.coverAreas;p.height=a.focusArea.height()/r.height(),p.width=a.focusArea.width()/r.width(),p.x=(n-e)/r.width(),p.y=(s-i)/r.height(),a.updatePreviewThumbnail(a.currentCropVariant,a.activeCropVariantTrigger),a.checkFocusAndCoverAreasCollision(p,u)?a.focusArea.addClass("has-nodrop"):a.focusArea.removeClass("has-nodrop")},stop:function(t,i){var o=r.offset(),n=o.left,s=o.top,c=a.focusArea.offset(),p=c.left,u=c.top,d=a.currentCropVariant,h=d.focusArea,l=d.coverAreas;a.checkFocusAndCoverAreasCollision(h,l)?i.element.animate(e.extend(i.originalPosition,i.originalSize),250,function(){h.height=a.focusArea.height()/r.height(),h.height=a.focusArea.height()/r.height(),h.width=a.focusArea.width()/r.width(),h.x=(p-n)/r.width(),h.y=(u-s)/r.height(),a.scaleAndMoveFocusArea(h),a.focusArea.removeClass("has-nodrop")}):a.scaleAndMoveFocusArea(h)}})},t.prototype.initCoverAreas=function(r,a){a.forEach(function(a){var i=e('<div class="cropper-cover-area t3js-cropper-cover-area"></div>');r.append(i),i.css({height:t.toCssPercent(a.height),left:t.toCssPercent(a.x),top:t.toCssPercent(a.y),width:t.toCssPercent(a.width)})})},t.prototype.updatePreviewThumbnail=function(r,e){var a,i=e.find(".t3js-cropper-preview-thumbnail-crop-area"),o=e.find(".t3js-cropper-preview-thumbnail-crop-image"),n=e.find(".t3js-cropper-preview-thumbnail-focus-area"),s=this.cropper.cropper("getImageData");i.css({height:t.toCssPercent(r.cropArea.height/s.naturalHeight),left:t.toCssPercent(r.cropArea.x/s.naturalWidth),top:t.toCssPercent(r.cropArea.y/s.naturalHeight),width:t.toCssPercent(r.cropArea.width/s.naturalWidth)}),r.focusArea&&n.css({height:t.toCssPercent(r.focusArea.height),left:t.toCssPercent(r.focusArea.x),top:t.toCssPercent(r.focusArea.y),width:t.toCssPercent(r.focusArea.width)}),a=i.css(["width","height","left","top"]),o.css({height:parseFloat(a.height)*(1/(r.cropArea.height/s.naturalHeight))+"px",margin:-1*parseFloat(a.left)+"px",marginTop:-1*parseFloat(a.top)+"px",width:parseFloat(a.width)*(1/(r.cropArea.width/s.naturalWidth))+"px"})},t.prototype.scaleAndMoveFocusArea=function(r){this.focusArea.css({height:t.toCssPercent(r.height),left:t.toCssPercent(r.x),top:t.toCssPercent(r.y),width:t.toCssPercent(r.width)}),this.currentCropVariant.focusArea=r,this.updatePreviewThumbnail(this.currentCropVariant,this.activeCropVariantTrigger),this.updateCropVariantData(this.currentCropVariant)},t.prototype.updateCropVariantData=function(t){var r=this.cropper.cropper("getImageData"),a=this.convertAbsoluteToRelativeCropArea(t.cropArea,r);this.data[t.id]=e.extend(!0,{},t,{cropArea:a})},t.prototype.setAspectRatio=function(t){this.cropper.cropper("setAspectRatio",t.value)},t.prototype.setCropArea=function(t){0===this.currentCropVariant.allowedAspectRatios[this.currentCropVariant.selectedRatio].value?this.cropper.cropper("setData",{height:t.height,width:t.width,x:t.x,y:t.y}):this.cropper.cropper("setData",{height:t.height,x:t.x,y:t.y})},t.prototype.checkFocusAndCoverAreasCollision=function(t,r){return!!r&&r.some(function(r){return t.x<r.x+r.width&&t.x+t.width>r.x&&t.y<r.y+r.height&&t.height+t.y>r.y})},t.prototype.convertAbsoluteToRelativeCropArea=function(t,r){var e=t.height,a=t.width,i=t.x,o=t.y;return{height:e/r.naturalHeight,width:a/r.naturalWidth,x:i/r.naturalWidth,y:o/r.naturalHeight}},t.prototype.convertRelativeToAbsoluteCropArea=function(t,r){var e=t.height,a=t.width,i=t.x,o=t.y;return{height:e*r.naturalHeight,width:a*r.naturalWidth,x:i*r.naturalWidth,y:o*r.naturalHeight}},t.prototype.setPreviewImages=function(t){var r=this,a=this.cropper,i=a.cropper("getImageData");Object.keys(t).forEach(function(o){var n=t[o],s=r.convertRelativeToAbsoluteCropArea(n.cropArea,i),c=r.trigger.closest(".form-group").find('.t3js-image-manipulation-preview[data-crop-variant-id="'+o+'"]'),p=r.trigger.closest(".form-group").find('.t3js-image-manipulation-selected-ratio[data-crop-variant-id="'+o+'"]');if(0!==c.length){var u=c.width(),d=c.data("preview-height"),h=s.width/s.height,l=u/h;l>d?u=d*h:d=l,u>s.width&&(u=s.width,d=s.height);var f=u/s.width,g=e("<div />").html('<img src="'+a.attr("src")+'">'),v=r.currentModal.find('.t3-js-ratio-title[data-ratio-id="'+n.id+n.selectedRatio+'"]');p.text(v.text()),g.addClass("cropper-preview-container"),c.empty().append(g),g.wrap('<span class="thumbnail thumbnail-status"></span>'),g.width(u).height(d).find("img").css({height:i.naturalHeight*f,left:-s.x*f,top:-s.y*f,width:i.naturalWidth*f})}})},t.prototype.openPreview=function(r){var e=t.serializeCropVariants(r),a=this.trigger.attr("data-preview-url");a=a+"&cropVariants="+encodeURIComponent(e),window.open(a,"TYPO3ImageManipulationPreview")},t.prototype.save=function(r){var a=t.serializeCropVariants(r),i=e("#"+this.trigger.attr("data-field"));this.trigger.attr("data-crop-variants",JSON.stringify(r)),this.setPreviewImages(r),i.val(a),this.currentModal.modal("hide")},t.prototype.destroy=function(){this.currentModal&&(void 0!==this.cropper&&null!==this.cropper&&this.cropper.cropper("destroy"),this.cropper=null,this.currentModal=null,this.data=null)},t.prototype.resizeEnd=function(t){var r,a=this;e(window).on("resize",function(){clearTimeout(r),r=setTimeout(function(){t()},a.resizeTimeout)})},t}())});
\ No newline at end of file