/* * This file is part of the TYPO3 CMS project. * * It is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, either version 2 * of the License, or any later version. * * For the full copyright and license information, please read the * LICENSE.txt file that was distributed with this source code. * * The TYPO3 project - inspiring people to share! */ import $ from 'jquery'; import 'jquery-ui/draggable'; import 'jquery-ui/resizable'; import {AjaxResponse} from 'TYPO3/CMS/Core/Ajax/AjaxResponse'; import FormEngineValidation = require('TYPO3/CMS/Backend/FormEngineValidation'); import AjaxRequest = require('TYPO3/CMS/Core/Ajax/AjaxRequest'); import Cropper from 'cropperjs'; import ImagesLoaded = require('imagesloaded'); import Icons = require('./Icons'); import Modal = require('./Modal'); import ThrottleEvent from 'TYPO3/CMS/Core/Event/ThrottleEvent'; interface Area { x: number; y: number; height: number; width: number; } interface Ratio { id: string; title: string; value: number; } interface CropVariant { title: string; id: string; selectedRatio: string; cropArea?: Area; focusArea?: Area; coverAreas?: Area[]; allowedAspectRatios: {[key: string]: Ratio}; } interface Offset { left: number; top: number; } interface CropperEvent extends CustomEvent { detail: Cropper.Data; } /** * Module: TYPO3/CMS/Backend/ImageManipulation * Contains all logic for the image crop GUI including setting focusAreas * @exports TYPO3/CMS/Backend/ImageManipulation */ class ImageManipulation { private initialized: boolean = false; private trigger: JQuery; private currentModal: JQuery; private cropVariantTriggers: JQuery; private activeCropVariantTrigger: JQuery; private saveButton: JQuery; private previewButton: JQuery; private dismissButton: JQuery; private resetButton: JQuery; private aspectRatioTrigger: JQuery; private cropInfo: JQuery; private cropImageContainerSelector: string = '#t3js-crop-image-container'; private imageOriginalSizeFactor: number; private cropImageSelector: string = '#t3js-crop-image'; private coverAreaSelector: string = '.t3js-cropper-cover-area'; private cropInfoSelector: string = '.t3js-cropper-info-crop'; private focusAreaSelector: string = '#t3js-cropper-focus-area'; private focusArea: any; private cropBox: JQuery; private cropper: Cropper; private currentCropVariant: CropVariant; private data: any; private defaultFocusArea: Area = { height: 1 / 3, width: 1 / 3, x: 0, y: 0, }; private defaultOpts: Cropper.Options = { autoCrop: true, autoCropArea: 0.7, dragMode: 'crop', guides: true, responsive: true, viewMode: 1, zoomable: false, checkCrossOrigin: false, }; private resizeTimeout: number = 450; /** * @method isCropAreaEmpty * @desc Checks if an area is set or pristine * @param {Area} area - The area to check * @return {boolean} * @static */ public static isEmptyArea(area: Area): boolean { return $.isEmptyObject(area); } /** * @method wait * @desc window.setTimeout shim * @param {Function} fn - The function to execute * @param {number} ms - The time in [ms] to wait until execution * @return {boolean} * @public * @static */ public static wait(fn: () => void, ms: number): void { window.setTimeout(fn, ms); } /** * @method toCssPercent * @desc Takes a number, and converts it to CSS percentage length * @param {number} num - The number to convert * @return {string} * @public * @static */ public static toCssPercent(num: number): string { return `${num * 100}%`; } /** * @method serializeCropVariants * @desc Serializes crop variants for persistence or preview * @param {Object} cropVariants * @returns string */ private static serializeCropVariants(cropVariants: object): string { const omitUnused: any = (key: any, value: any) => { return ( key === 'id' || key === 'title' || key === 'allowedAspectRatios' || key === 'coverAreas' ) ? undefined : value; }; return JSON.stringify(cropVariants, omitUnused); } constructor() { // silence is golden $(window).on('resize', (): void => { if (this.cropper) { this.cropper.destroy(); } }); new ThrottleEvent('resize', (): void => { if (this.cropper) { this.init(); } }, this.resizeTimeout).bindTo(window) } /** * @method initializeTrigger * @desc Assign a handler to .t3js-image-manipulation-trigger. * Show the modal and kick-off image manipulation * @public */ public initializeTrigger(): void { const triggerHandler = (e: JQueryEventObject): void => { e.preventDefault(); this.trigger = $(e.currentTarget); this.show(); }; $('.t3js-image-manipulation-trigger').off('click').on('click', triggerHandler); } /** * @method initializeCropperModal * @desc Initialize the cropper modal and dispatch the cropper init * @private */ private initializeCropperModal(): void { const image: JQuery = this.currentModal.find(this.cropImageSelector); ImagesLoaded(image.get(0), (): void => { this.init(); }); } /** * @method show * @desc Load the image and setup the modal UI * @private */ private show(): void { const modalTitle: string = this.trigger.data('modalTitle'); const buttonPreviewText: string = this.trigger.data('buttonPreviewText'); 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'); Icons.getIcon('spinner-circle', Icons.sizes.default, null, null, Icons.markupIdentifiers.inline).then((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, }, { btnClass: 'btn-default', dataAttributes: { method: 'dismiss', }, icon: 'actions-close', text: buttonDismissText, }, { btnClass: 'btn-primary', dataAttributes: { method: 'save', }, icon: 'actions-document-save', text: buttonSaveText, }, ], content: $('