(function () { var templateFields = document.getElementById('template-fields'); var modal = document.getElementById('slide-image-cropper-modal'); var image = document.getElementById('slide-image-cropper-image'); var status = document.getElementById('slide-image-cropper-status'); if (!templateFields || !modal || !image || !status) { return; } var cropper = null; var currentInput = null; var currentFile = null; var currentObjectUrl = ''; var flipX = 1; var flipY = 1; var currentAspectRatio = NaN; var currentRegionAspectRatio = NaN; var currentRegionAspectRatioLabel = 'Region'; var listenersAttached = false; function getRegionId(input) { var match = String(input && input.name || '').match(/^region_image_(\d+)$/); return match ? match[1] : ''; } function getExistingInput(regionId) { if (!regionId) { return null; } return document.querySelector('input[type="hidden"][name="existing_region_image_' + regionId + '"]'); } function setStatus(message) { status.textContent = String(message || ''); } function showModal() { if (window.pulseModal && typeof window.pulseModal.show === 'function') { window.pulseModal.show(modal); return; } if (window.bootstrap && window.bootstrap.Modal) { window.bootstrap.Modal.getOrCreateInstance(modal).show(); } } function hideModal() { if (window.pulseModal && typeof window.pulseModal.hide === 'function') { window.pulseModal.hide(modal); return; } if (window.bootstrap && window.bootstrap.Modal) { window.bootstrap.Modal.getOrCreateInstance(modal).hide(); } } function destroyCropper() { if (cropper) { cropper.destroy(); cropper = null; } } function revokeObjectUrl() { if (currentObjectUrl) { URL.revokeObjectURL(currentObjectUrl); currentObjectUrl = ''; } } function resetModalState() { destroyCropper(); revokeObjectUrl(); currentInput = null; currentFile = null; flipX = 1; flipY = 1; image.removeAttribute('src'); image.alt = 'Selected image to crop'; setStatus(''); } function setButtonState(disabled) { modal.querySelectorAll('[data-slide-image-cropper-action]').forEach(function (button) { button.disabled = Boolean(disabled) && button.getAttribute('data-slide-image-cropper-action') !== 'reset'; }); } function ratioLabelForValue(value) { if (value === 'free') { return 'free'; } if (value === 'region') { return 'region'; } return String(value || '').trim(); } function parseAspectRatio(value) { var parts = String(value || '').trim().split(/[:/]/); var width = Number(parts[0]); var height = Number(parts[1]); if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) { return NaN; } return width / height; } function setActiveRatioButton(value) { var targetValue = ratioLabelForValue(value); modal.querySelectorAll('[data-slide-image-cropper-action="ratio"]').forEach(function (button) { var buttonValue = ratioLabelForValue(button.getAttribute('data-slide-image-cropper-ratio')); var isActive = buttonValue === targetValue; button.classList.toggle('active', isActive); button.setAttribute('aria-pressed', isActive ? 'true' : 'false'); }); } function applyAspectRatio(value) { if (!cropper) { return; } currentAspectRatio = value; if (value === 'free') { cropper.setAspectRatio(NaN); setActiveRatioButton('free'); return; } if (value === 'region') { if (!Number.isFinite(currentRegionAspectRatio) || currentRegionAspectRatio <= 0) { cropper.setAspectRatio(NaN); setActiveRatioButton('free'); return; } cropper.setAspectRatio(currentRegionAspectRatio); setActiveRatioButton('region'); return; } var parts = String(value || '').split(':'); var width = Number(parts[0]); var height = Number(parts[1]); if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) { return; } cropper.setAspectRatio(width / height); setActiveRatioButton(value); } function initCropper() { if (!currentInput || !currentFile || !window.Cropper) { return; } destroyCropper(); cropper = new window.Cropper(image, { aspectRatio: NaN, autoCropArea: 1, background: false, dragMode: 'move', initialAspectRatio: NaN, movable: true, responsive: true, rotatable: true, scalable: true, viewMode: 1, zoomOnTouch: true, zoomOnWheel: true, ready: function () { if (cropper && cropper.container) { cropper.container.style.width = '100%'; cropper.container.style.height = '560px'; cropper.container.style.maxHeight = '70vh'; } var containerData = cropper.getContainerData(); var imageData = cropper.getImageData(); var fitRatio = Math.min( Number(containerData.width || 0) / Number(imageData.naturalWidth || 1), Number(containerData.height || 0) / Number(imageData.naturalHeight || 1), 1 ); if (Number.isFinite(fitRatio) && fitRatio > 0) { cropper.zoomTo(fitRatio); } else { cropper.reset(); } applyAspectRatio(currentAspectRatio === undefined ? 'free' : currentAspectRatio); } }); setButtonState(false); setActiveRatioButton(currentAspectRatio === undefined ? 'free' : currentAspectRatio); setStatus('Use the toolbar to crop, rotate, or flip the image before applying it.'); } function openEditor(input, file) { currentInput = input; currentFile = file; flipX = 1; flipY = 1; currentAspectRatio = 'free'; currentRegionAspectRatio = parseAspectRatio(input && input.dataset && input.dataset.slideImageCropperRegionRatio); currentRegionAspectRatioLabel = String(input && input.dataset && input.dataset.slideImageCropperRegionRatioLabel || 'Region').trim() || 'Region'; setButtonState(true); setStatus('Loading image editor...'); modal.querySelectorAll('[data-slide-image-cropper-action="ratio"][data-slide-image-cropper-ratio="region"]').forEach(function (button) { button.title = currentRegionAspectRatioLabel ? 'Region ratio ' + currentRegionAspectRatioLabel : 'Region ratio'; }); revokeObjectUrl(); currentObjectUrl = URL.createObjectURL(file); image.alt = file.name || 'Selected image to crop'; image.src = currentObjectUrl; if (!listenersAttached) { listenersAttached = true; modal.addEventListener('shown.bs.modal', function () { if (currentInput && currentFile) { initCropper(); } }); modal.addEventListener('hidden.bs.modal', function () { resetModalState(); }); } showModal(); } function setInputFile(input, file) { var dataTransfer = new DataTransfer(); dataTransfer.items.add(file); input.files = dataTransfer.files; } function finalizeCropFile(file) { if (!currentInput) { return; } var input = currentInput; var existing = getExistingInput(getRegionId(input)); input.dataset.cropperBypass = '1'; setInputFile(input, file); if (existing) { existing.value = ''; } input.dispatchEvent(new Event('change', { bubbles: true })); window.setTimeout(function () { delete input.dataset.cropperBypass; }, 0); hideModal(); } function commitCrop() { if (!currentInput || !currentFile) { return; } if (!cropper) { finalizeCropFile(currentFile); return; } setButtonState(true); setStatus('Creating the cropped image...'); var canvas = cropper.getCroppedCanvas({ fillColor: 'transparent', imageSmoothingEnabled: true, imageSmoothingQuality: 'high' }); if (!canvas) { setStatus('Unable to create the cropped image.'); setButtonState(false); return; } var outputType = /^image\/(jpeg|jpg|webp|png)$/i.test(currentFile.type || '') ? currentFile.type : 'image/png'; canvas.toBlob(function (blob) { if (!blob) { setStatus('Unable to create the cropped image.'); setButtonState(false); return; } var finalFile = new File([blob], currentFile.name || 'slide-region-image.png', { lastModified: Date.now(), type: blob.type || outputType }); finalizeCropFile(finalFile); }, outputType, outputType.indexOf('jpeg') !== -1 ? 0.92 : undefined); } function rotateImage(direction) { if (cropper) { cropper.rotate(direction); } } function flipImage(axis) { if (!cropper) { return; } if (axis === 'x') { flipX *= -1; cropper.scaleX(flipX); return; } flipY *= -1; cropper.scaleY(flipY); } function resetImage() { if (!cropper) { return; } cropper.reset(); flipX = 1; flipY = 1; } templateFields.addEventListener('change', function (event) { var input = event.target; if (!input || !input.matches || !input.matches('input[type="file"][name^="region_image_"]')) { return; } if (input.dataset.cropperBypass === '1') { return; } var file = input.files && input.files[0]; if (!file) { return; } if (!/^image\//i.test(file.type || '')) { input.value = ''; return; } event.stopImmediatePropagation(); event.stopPropagation(); input.value = ''; if (!window.Cropper) { currentInput = input; currentFile = file; commitCrop(); return; } openEditor(input, file); }, true); modal.addEventListener('click', function (event) { var button = event.target && event.target.closest ? event.target.closest('[data-slide-image-cropper-action]') : null; if (!button) { return; } var action = button.getAttribute('data-slide-image-cropper-action'); if (action === 'apply') { commitCrop(); return; } if (action === 'ratio') { applyAspectRatio(button.getAttribute('data-slide-image-cropper-ratio') || 'free'); return; } if (action === 'rotate-left') { rotateImage(-90); return; } if (action === 'rotate-right') { rotateImage(90); return; } if (action === 'flip-horizontal') { flipImage('x'); return; } if (action === 'flip-vertical') { flipImage('y'); return; } if (action === 'reset') { resetImage(); return; } }); }());