[BUGFIX] Fix broken ImageManipulation wizard
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / ImageManipulation.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 /**
15 * Module: TYPO3/CMS/Backend/ImageManipulation
16 * Contains all logic for the image crop GUI
17 */
18 define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], function ($, Modal, Severity) {
19
20 /**
21 *
22 * @type {{margin: number, currentModal: null, cropperSelector: string, $trigger: null}}
23 * @exports TYPO3/CMS/Backend/ImageManipulation
24 */
25 var ImageManipulation = {
26 margin: 20,
27 currentModal: null,
28 cropperSelector: '.t3js-cropper-image-container > img',
29 $trigger: null
30 };
31
32 /**
33 * Initialize triggers
34 */
35 ImageManipulation.initializeTrigger = function() {
36 var $triggers = $('.t3js-image-manipulation-trigger');
37 // Remove existing bind function
38 $triggers.off('click', ImageManipulation.buttonClick);
39 // Bind new function
40 $triggers.on('click', ImageManipulation.buttonClick);
41 };
42
43 /**
44 * Functions that should be bind to the trigger button
45 *
46 * @param {Event} e click event
47 */
48 ImageManipulation.buttonClick = function(e) {
49 e.preventDefault();
50 // Prevent double trigger
51 if (ImageManipulation.$trigger !== $(this)) {
52 ImageManipulation.$trigger = $(this);
53 ImageManipulation.show();
54 }
55 };
56
57 /**
58 * Open modal with image to crop
59 */
60 ImageManipulation.show = function() {
61 ImageManipulation.currentModal = Modal.loadUrl(
62 ImageManipulation.$trigger.data('image-name'),
63 Severity.notice,
64 [],
65 ImageManipulation.$trigger.data('url'),
66 ImageManipulation.initializeCropperModal,
67 '.modal-content'
68 );
69 ImageManipulation.currentModal.addClass('modal-dark');
70 };
71
72 /**
73 * Initialize the cropper modal
74 */
75 ImageManipulation.initializeCropperModal = function() {
76 top.require(['cropper', 'imagesloaded'], function(cropperJs, imagesLoaded) {
77 var $image = ImageManipulation.getCropper();
78
79 // wait until image is loaded
80 imagesLoaded($image, function() {
81 var $modal = ImageManipulation.currentModal.find('.modal-dialog');
82 var $modalContent = $modal.find('.modal-content');
83 var $modalPanelSidebar = $modal.find('.modal-panel-sidebar');
84 var $modalPanelBody = $modal.find('.modal-panel-body');
85 // Let modal auto-fill width
86 $modal.css({width:'auto', marginLeft: ImageManipulation.margin, marginRight: ImageManipulation.margin})
87 .addClass('modal-image-manipulation modal-resize');
88
89 $modalContent.addClass('cropper-bg');
90
91 // Determine available height
92 var height = top.TYPO3.jQuery(window).height()
93 - (ImageManipulation.margin * 4);
94 $image.css({maxHeight: height});
95
96 // Wait a few microseconds before calculating available width (DOM isn't always updated direct)
97 setTimeout(function() {
98 $modalPanelBody.css({width: $modalContent.innerWidth() - $modalPanelSidebar.outerWidth() - (ImageManipulation.margin * 2)});
99
100 setTimeout(function() {
101 // Shrink modal when possible (the set left/right margin + width auto above makes it fill 100%)
102 var minWidth = Math.max(500, $image.outerWidth() + $modalPanelSidebar.outerWidth() + (ImageManipulation.margin * 2));
103 var width = $modal.width() > minWidth ? minWidth : $modal.width();
104 $modal.width(width);
105 $modalPanelBody.width(width - $modalPanelSidebar.outerWidth() - (ImageManipulation.margin * 4));
106
107 var modalBodyMinHeight = $modalContent.height() -
108 ($modalPanelSidebar.find('.modal-header').outerHeight() + $modalPanelSidebar.find('.modal-body-footer').outerHeight());
109 $modalPanelSidebar.find('.modal-body').css('min-height', modalBodyMinHeight);
110
111 // Center modal horizontal
112 $modal.css({marginLeft: 'auto', marginRight: 'auto'});
113
114 // Center modal vertical
115 Modal.center();
116
117 // Wait a few microseconds to let the modal resize
118 setTimeout(ImageManipulation.initializeCropper, 100);
119 }, 100);
120
121 }, 100);
122 });
123
124 });
125 };
126
127 /**
128 * Initialize cropper
129 */
130 ImageManipulation.initializeCropper = function() {
131 var $image = ImageManipulation.getCropper(), cropData;
132
133 // Give img-container same dimensions as the image
134 ImageManipulation.currentModal.find('.t3js-cropper-image-container').
135 css({width: $image.width(), height: $image.height()});
136
137 var $trigger = ImageManipulation.$trigger;
138 var jsonString = $trigger.parent().find('#' + $trigger.data('field')).val();
139 if (jsonString.length) {
140 cropData = $.parseJSON(jsonString);
141 }
142
143 var $infoX = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-x');
144 var $infoY = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-y');
145 var $infoWidth = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-width');
146 var $infoHeight = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-height');
147
148 $image.cropper({
149 autoCropArea: 0.5,
150 strict: false,
151 zoomable: ImageManipulation.currentModal.find('.t3js-setting-zoom').length > 0,
152 built: function() {
153 if (cropData) {
154 // Dimensions CropBox need to be the real visible dimensions
155 var ratio = $image.cropper('getImageData').width / $image.data('original-width');
156 var cropBox = {};
157 cropBox.left = cropData.x * ratio;
158 cropBox.top = cropData.y * ratio;
159 cropBox.width = cropData.width * ratio;
160 cropBox.height = cropData.height * ratio;
161 $image.cropper('setCropBoxData', cropBox);
162 }
163 },
164 crop: function (data) {
165 var ratio = $image.cropper('getImageData').naturalWidth / $image.data('original-width');
166 $infoX.text(Math.round(data.x / ratio) + 'px');
167 $infoY.text(Math.round(data.y / ratio) + 'px');
168 $infoWidth.text(Math.round(data.width / ratio) + 'px');
169 $infoHeight.text(Math.round(data.height / ratio) + 'px');
170 }
171 });
172
173 // Destroy cropper when modal is closed
174 ImageManipulation.currentModal.on('hidden.bs.modal', function() {
175 $image.cropper('destroy');
176 });
177
178 ImageManipulation.initializeCroppingActions();
179 };
180
181 /**
182 * Get image to be cropped
183 *
184 * @returns {Object} jQuery object
185 */
186 ImageManipulation.getCropper = function() {
187 return ImageManipulation.currentModal.find(ImageManipulation.cropperSelector);
188 };
189
190 /**
191 * Bind buttons from cropper tool panel
192 */
193 ImageManipulation.initializeCroppingActions = function() {
194 ImageManipulation.currentModal.find('[data-method]').click(function(e) {
195 e.preventDefault();
196 var method = $(this).data('method');
197 var options = $(this).data('option') || {};
198 if (typeof ImageManipulation[method] === 'function') {
199 ImageManipulation[method](options);
200 }
201 });
202 };
203
204 /**
205 * Change the aspect ratio of the crop box
206 *
207 * @param {Number} aspectRatio
208 */
209 ImageManipulation.setAspectRatio = function(aspectRatio) {
210 var $cropper = ImageManipulation.getCropper();
211 $cropper.cropper('setAspectRatio', aspectRatio);
212 };
213
214 /**
215 * Set zoom ratio
216 *
217 * Zoom in: requires a positive number (ratio > 0)
218 * Zoom out: requires a negative number (ratio < 0)
219 *
220 * @param {Number} ratio
221 */
222 ImageManipulation.zoom = function(ratio) {
223 var $cropper = ImageManipulation.getCropper();
224 $cropper.cropper('zoom', ratio);
225 };
226
227 /**
228 * Save crop values in form and close modal
229 */
230 ImageManipulation.save = function() {
231 var $image = ImageManipulation.getCropper();
232 var $trigger = ImageManipulation.$trigger;
233 var formFieldId = $trigger.data('field');
234 var $formField = $trigger.parent().find('#' + formFieldId);
235 var $formGroup = $formField.closest('.form-group');
236 var cropData = $image.cropper('getData');
237 var newValue = '';
238 $formGroup.addClass('has-change');
239 if (cropData.width > 0 && cropData.height > 0) {
240 var ratio = $image.cropper('getImageData').naturalWidth / $image.data('original-width');
241 cropData.x = cropData.x / ratio;
242 cropData.y = cropData.y / ratio;
243 cropData.width = cropData.width / ratio;
244 cropData.height = cropData.height / ratio;
245 newValue = JSON.stringify(cropData);
246 $formGroup.find('.t3js-image-manipulation-info').removeClass('hide');
247 $formGroup.find('.t3js-image-manipulation-info-crop-x').text(Math.round(cropData.x) + 'px');
248 $formGroup.find('.t3js-image-manipulation-info-crop-y').text(Math.round(cropData.y) + 'px');
249 $formGroup.find('.t3js-image-manipulation-info-crop-width').text(Math.round(cropData.width) + 'px');
250 $formGroup.find('.t3js-image-manipulation-info-crop-height').text(Math.round(cropData.height) + 'px');
251 $formGroup.find('.t3js-image-manipulation-preview').removeClass('hide');
252 ImageManipulation.setPreviewImage();
253 } else {
254 $formGroup.find('.t3js-image-manipulation-info').addClass('hide');
255 $formGroup.find('.t3js-image-manipulation-preview').addClass('hide');
256 }
257 $formField.val(newValue);
258 ImageManipulation.dismiss();
259 };
260
261 /**
262 * Reset crop selection
263 */
264 ImageManipulation.reset = function() {
265 var $image = ImageManipulation.getCropper();
266 $image.cropper('clear');
267 };
268
269 /**
270 * Close the current open modal
271 */
272 ImageManipulation.dismiss = function() {
273 if (ImageManipulation.currentModal) {
274 ImageManipulation.currentModal.modal('hide');
275 ImageManipulation.currentModal = null;
276 }
277 };
278
279 /**
280 * Set preview image
281 */
282 ImageManipulation.setPreviewImage = function() {
283 var $preview = ImageManipulation.$trigger.closest('.form-group').find('.t3js-image-manipulation-preview');
284 if ($preview.length === 0) {
285 return;
286 }
287 var $image = ImageManipulation.getCropper();
288 var imageData = $image.cropper('getImageData');
289 var cropData = $image.cropper('getData');
290 var previewWidth = $preview.data('preview-width');
291 var previewHeight = $preview.data('preview-height');
292
293 // Adjust aspect ratio of preview width/height
294 var aspectRatio = cropData.width / cropData.height;
295 var tmpHeight = previewWidth / aspectRatio;
296 if (tmpHeight > previewHeight) {
297 previewWidth = previewHeight * aspectRatio;
298 } else {
299 previewHeight = tmpHeight;
300 }
301 // preview should never be up-scaled
302 if (previewWidth > cropData.width) {
303 previewWidth = cropData.width;
304 previewHeight = cropData.height;
305 }
306
307 var ratio = previewWidth / cropData.width;
308
309 var $viewBox = $('<div />').html('<img src="' + $image.attr('src') + '">');
310 $viewBox.addClass('cropper-preview-container');
311 $preview.empty().append($viewBox);
312 $viewBox.wrap('<span class="thumbnail thumbnail-status"></span>');
313
314 $viewBox.width(previewWidth).height(previewHeight).find('img').css({
315 width: imageData.naturalWidth * ratio,
316 height: imageData.naturalHeight * ratio,
317 left: -cropData.x * ratio,
318 top: -cropData.y * ratio
319 });
320 };
321
322 return ImageManipulation;
323 });