(function ($) { 'use strict'; $(document).ready(function () { var $uploadWrap = $('.studiou-fpp-upload-wrap'); if (!$uploadWrap.length) { return; } var productId = $uploadWrap.data('product-id'); var maxFileSize = parseInt($uploadWrap.data('max-file-size'), 10) || 50; var chunkSize = parseInt(studiouWcfppFront.chunkSize, 10) || 1048576; var uploadedAttachmentId = null; var uploadedFileRecordId = null; var isUploading = false; var originalGalleryImages = {}; // Upload area elements var $dropzone = $uploadWrap.find('#studiou-fpp-dropzone'); var $fileInput = $uploadWrap.find('#studiou-fpp-file-input'); var $progress = $uploadWrap.find('.studiou-fpp-progress'); var $progressFill = $uploadWrap.find('.studiou-fpp-progress-fill'); var $progressText = $uploadWrap.find('.studiou-fpp-progress-text'); var $preview = $uploadWrap.find('.studiou-fpp-preview'); var $previewImg = $preview.find('img'); var $previewName = $uploadWrap.find('.studiou-fpp-preview-name'); var $removeBtn = $uploadWrap.find('.studiou-fpp-remove-file'); var $messages = $uploadWrap.find('.studiou-fpp-messages'); // ========================================== // Restore session state on page load // ========================================== if (typeof studiouWcfppSession !== 'undefined' && studiouWcfppSession.attachment_id) { uploadedAttachmentId = studiouWcfppSession.attachment_id; uploadedFileRecordId = studiouWcfppSession.file_record_id; showPreview(studiouWcfppSession.thumbnail_url, studiouWcfppSession.file_name); updateProductGallery(studiouWcfppSession.preview_url || studiouWcfppSession.thumbnail_url); } // ========================================== // Drag and drop // ========================================== $dropzone.on('dragover dragenter', function (e) { e.preventDefault(); e.stopPropagation(); $(this).addClass('studiou-fpp-dragover'); }); $dropzone.on('dragleave drop', function (e) { e.preventDefault(); e.stopPropagation(); $(this).removeClass('studiou-fpp-dragover'); }); $dropzone.on('drop', function (e) { var files = e.originalEvent.dataTransfer.files; if (files.length > 0) { handleFile(files[0]); } }); $dropzone.on('click', function () { if (!isUploading) { $fileInput.trigger('click'); } }); $fileInput.on('change', function () { if (this.files.length > 0) { handleFile(this.files[0]); } }); // Remove file $removeBtn.on('click', function () { removeUploadedFile(); }); // ========================================== // Upload logic // ========================================== function handleFile(file) { if (isUploading) return; var maxBytes = maxFileSize * 1024 * 1024; if (file.size > maxBytes) { showMessage(studiouWcfppFront.i18n.fileTooLarge.replace('%s', maxFileSize), 'error'); return; } clearMessages(); startChunkedUpload(file); } function startChunkedUpload(file) { isUploading = true; var totalChunks = Math.ceil(file.size / chunkSize); var uploadId = 'upload_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); var currentChunk = 0; $dropzone.hide(); $progress.show(); function uploadNextChunk() { var start = currentChunk * chunkSize; var end = Math.min(start + chunkSize, file.size); var blob = file.slice(start, end); var formData = new FormData(); formData.append('action', 'studiou_wcfpp_upload_chunk'); formData.append('nonce', studiouWcfppFront.nonce); formData.append('product_id', productId); formData.append('upload_id', uploadId); formData.append('chunk_index', currentChunk); formData.append('total_chunks', totalChunks); formData.append('file_name', file.name); formData.append('file_size', file.size); formData.append('chunk', blob, 'chunk'); var isLastChunk = (currentChunk === totalChunks - 1); if (isLastChunk) { $progressFill.css('width', '100%'); $progressText.text(studiouWcfppFront.i18n.processing || 'Processing...'); } $.ajax({ url: studiouWcfppFront.ajaxUrl, type: 'POST', data: formData, processData: false, contentType: false, timeout: isLastChunk ? 120000 : 30000, success: function (response) { if (response.success) { if (!isLastChunk) { currentChunk++; var percent = Math.round((currentChunk / totalChunks) * 100); $progressFill.css('width', percent + '%'); $progressText.text(percent + '%'); } if (response.data.complete) { uploadedAttachmentId = response.data.attachment_id; uploadedFileRecordId = response.data.file_record_id; storeInSession( response.data.attachment_id, response.data.file_record_id, response.data.file_name ); showPreview(response.data.thumbnail_url, response.data.file_name); updateProductGallery(response.data.preview_url || response.data.thumbnail_url); $progress.hide(); isUploading = false; } else { uploadNextChunk(); } } else { uploadFailed(response.data ? response.data.message : studiouWcfppFront.i18n.uploadError); } }, error: function () { uploadFailed(studiouWcfppFront.i18n.uploadError); } }); } uploadNextChunk(); } // ========================================== // Session management // ========================================== function storeInSession(attachmentId, fileRecordId, fileName) { $.ajax({ url: studiouWcfppFront.ajaxUrl, type: 'POST', data: { action: 'studiou_wcfpp_set_upload_session', nonce: studiouWcfppFront.nonce, attachment_id: attachmentId, file_record_id: fileRecordId, file_name: fileName } }); } function clearSession() { $.ajax({ url: studiouWcfppFront.ajaxUrl, type: 'POST', data: { action: 'studiou_wcfpp_clear_upload_session', nonce: studiouWcfppFront.nonce } }); } // ========================================== // Product gallery image replacement // ========================================== function updateProductGallery(imageUrl) { if (!imageUrl) return; var selectors = [ '.woocommerce-product-gallery__image img', '.product .wp-post-image', '.product-image img', '.product-thumbnail img', '.entry-summary img.attachment-woocommerce_thumbnail', '.product-gallery img:first' ]; var $img = null; for (var i = 0; i < selectors.length; i++) { $img = $(selectors[i]).first(); if ($img.length) break; } if (!$img || !$img.length) return; if (!originalGalleryImages.src) { originalGalleryImages.src = $img.attr('src'); originalGalleryImages.srcset = $img.attr('srcset') || ''; originalGalleryImages.sizes = $img.attr('sizes') || ''; var $link = $img.closest('a'); if ($link.length) originalGalleryImages.href = $link.attr('href'); } $img.attr('src', imageUrl).removeAttr('srcset').removeAttr('sizes'); var $link = $img.closest('a'); if ($link.length) $link.attr('href', imageUrl); } function restoreProductGallery() { if (!originalGalleryImages.src) return; var selectors = [ '.woocommerce-product-gallery__image img', '.product .wp-post-image', '.product-image img', '.product-thumbnail img', '.entry-summary img.attachment-woocommerce_thumbnail', '.product-gallery img:first' ]; var $img = null; for (var i = 0; i < selectors.length; i++) { $img = $(selectors[i]).first(); if ($img.length) break; } if (!$img || !$img.length) return; $img.attr('src', originalGalleryImages.src); if (originalGalleryImages.srcset) $img.attr('srcset', originalGalleryImages.srcset); if (originalGalleryImages.sizes) $img.attr('sizes', originalGalleryImages.sizes); if (originalGalleryImages.href) $img.closest('a').attr('href', originalGalleryImages.href); originalGalleryImages = {}; } // ========================================== // UI helpers // ========================================== function uploadFailed(message) { isUploading = false; $progress.hide(); $dropzone.show(); showMessage(message, 'error'); $fileInput.val(''); } function showPreview(thumbnailUrl, fileName) { if (thumbnailUrl) { $previewImg.attr('src', thumbnailUrl).show(); } else { $previewImg.hide(); } $previewName.text(fileName); $preview.show(); $dropzone.hide(); } function removeUploadedFile() { if (!uploadedFileRecordId) return; $.ajax({ url: studiouWcfppFront.ajaxUrl, type: 'POST', data: { action: 'studiou_wcfpp_remove_upload', nonce: studiouWcfppFront.nonce, file_record_id: uploadedFileRecordId }, success: function () { resetUpload(); } }); } function resetUpload() { uploadedAttachmentId = null; uploadedFileRecordId = null; clearSession(); restoreProductGallery(); $preview.hide(); $previewImg.attr('src', ''); $previewName.text(''); $dropzone.show(); $fileInput.val(''); clearMessages(); } function showMessage(text, type) { var cls = type === 'error' ? 'studiou-fpp-msg-error' : 'studiou-fpp-msg-success'; $messages.html('
| ' + escHtml(i18n.qtyTiersFromQty || 'From Qty') + ' | '; html += '' + escHtml(i18n.qtyTiersDiscount || 'Discount') + ' | '; html += '' + escHtml(i18n.qtyTiersUnitPrice || 'Unit Price') + ' | '; html += '
|---|---|---|
| ' + t.from_qty + '+ | '; html += '' + formatPercent(t.percent) + ' | '; html += '' + formatPrice(unit) + ' | '; html += '