diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js index 36623bfb89dc7..784c5d5bacb3e 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js @@ -144,6 +144,9 @@ define([ fileId = null, arrayFromObj = Array.from, fileObj = [], + filePositions = {}, + uploadedFiles = {}, + totalFilesToUpload = 0, uploaderContainer = this.element.find('input[type="file"]').closest('.image-placeholder'), options = { proudlyDisplayPoweredByUppy: false, @@ -192,7 +195,12 @@ define([ }; uploaderContainer.addClass('loading'); + + // Track file selection order for maintaining upload order + const filePosition = fileObj.length; + filePositions[modifiedFile.id] = filePosition; fileObj.push(currentFile); + return modifiedFile; }, @@ -230,6 +238,15 @@ define([ $dropPlaceholder.find('.progress-bar').addClass('in-progress').text(progressWidth + '%'); }); + uppy.on('files-added', (files) => { + totalFilesToUpload = files.length; + uploadedFiles = {}; + + // Reset file tracking arrays + fileObj = []; + filePositions = {}; + }); + uppy.on('upload-success', (file, response) => { $dropPlaceholder.find('.progress-bar').text('').removeClass('in-progress'); @@ -238,7 +255,23 @@ define([ } if (!response.body.error) { - $galleryContainer.trigger('addItem', response.body); + // Store upload result with position info + const position = filePositions[file.id]; + uploadedFiles[position] = response.body; + + // Check if all files have been uploaded + if (Object.keys(uploadedFiles).length === totalFilesToUpload) { + // Add images in correct order (by position) + const sortedPositions = Object.keys(uploadedFiles).sort((a, b) => parseInt(a) - parseInt(b)); + + sortedPositions.forEach(position => { + $galleryContainer.trigger('addItem', uploadedFiles[position]); + }); + + // Reset for next batch + uploadedFiles = {}; + totalFilesToUpload = 0; + } } else { alert({ content: $.mage.__('We don\'t recognize or support this file extension type.') @@ -246,9 +279,42 @@ define([ } }); + uppy.on('upload-error', (file, error, response) => { + $dropPlaceholder.find('.progress-bar').text('').removeClass('in-progress'); + + // Reduce total count since this file failed + totalFilesToUpload--; + + // Check if remaining successful uploads should be processed + if (Object.keys(uploadedFiles).length === totalFilesToUpload && totalFilesToUpload > 0) { + const sortedPositions = Object.keys(uploadedFiles).sort((a, b) => parseInt(a) - parseInt(b)); + + sortedPositions.forEach(position => { + $galleryContainer.trigger('addItem', uploadedFiles[position]); + }); + + // Reset for next batch + uploadedFiles = {}; + totalFilesToUpload = 0; + } + }); + uppy.on('complete', () => { uploaderContainer.removeClass('loading'); Array.from = arrayFromObj; + + // Handle any remaining uploaded files if some uploads failed + if (Object.keys(uploadedFiles).length > 0 && Object.keys(uploadedFiles).length < totalFilesToUpload) { + const sortedPositions = Object.keys(uploadedFiles).sort((a, b) => parseInt(a) - parseInt(b)); + + sortedPositions.forEach(position => { + $galleryContainer.trigger('addItem', uploadedFiles[position]); + }); + + // Reset for next batch + uploadedFiles = {}; + totalFilesToUpload = 0; + } }); } }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/adminhtml/js/base-image-uploader.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/adminhtml/js/base-image-uploader.test.js new file mode 100644 index 0000000000000..e37de8a999f90 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/adminhtml/js/base-image-uploader.test.js @@ -0,0 +1,112 @@ +/** + * Copyright 2024 Adobe + * All Rights Reserved. + */ + +/*eslint-disable max-nested-callbacks*/ +/*jscs:disable jsDoc*/ +define([ + 'jquery', + 'Magento_Catalog/catalog/base-image-uploader' +], function ($) { + 'use strict'; + + describe('Base Image Uploader', function () { + var uploaderEl, + defaultConfig = { + maxImageUploadCount: 10 + }; + + beforeEach(function () { + uploaderEl = $( + '
' + + '' + + '
' + + '
' + + '
' + + '
' + ); + + uploaderEl.appendTo('body'); + }); + + afterEach(function () { + uploaderEl.remove(); + }); + + it('should preserve file selection order during upload', function () { + var uploader = uploaderEl.baseImage(defaultConfig), + filePositions, + uploadedFiles; + + // Simulate the internal tracking variables + filePositions = {}; + uploadedFiles = {}; + + // Test file order tracking + var testFiles = [ + { id: 'file1', name: 'first.jpg' }, + { id: 'file2', name: 'second.jpg' }, + { id: 'file3', name: 'third.jpg' } + ]; + + // Simulate onBeforeFileAdded behavior + testFiles.forEach(function (file, index) { + filePositions[file.id] = index; + }); + + // Simulate upload success in random order + var uploadOrder = [1, 0, 2]; // files complete in different order + var mockResponses = [ + { body: { file: 'first.jpg', position: 0 } }, + { body: { file: 'second.jpg', position: 1 } }, + { body: { file: 'third.jpg', position: 2 } } + ]; + + // Store results as they would complete + uploadOrder.forEach(function (fileIndex) { + var file = testFiles[fileIndex]; + var position = filePositions[file.id]; + uploadedFiles[position] = mockResponses[fileIndex]; + }); + + // Verify ordering logic + var sortedPositions = Object.keys(uploadedFiles).sort(function (a, b) { + return parseInt(a, 10) - parseInt(b, 10); + }); + + var expectedOrder = ['0', '1', '2']; + expect(sortedPositions).toEqual(expectedOrder); + + // Verify the files would be added in correct order + var actualFiles = sortedPositions.map(function (position) { + return uploadedFiles[position].file; + }); + + expect(actualFiles).toEqual(['first.jpg', 'second.jpg', 'third.jpg']); + }); + + it('should handle partial upload failures while maintaining order', function () { + var filePositions = { 'file1': 0, 'file2': 1, 'file3': 2 }; + var uploadedFiles = { + '0': { file: 'first.jpg' }, + '2': { file: 'third.jpg' } + // file2 failed to upload + }; + + var totalFilesToUpload = 2; // reduced after file2 failed + + var sortedPositions = Object.keys(uploadedFiles).sort(function (a, b) { + return parseInt(a, 10) - parseInt(b, 10); + }); + + var actualFiles = sortedPositions.map(function (position) { + return uploadedFiles[position].file; + }); + + // Should maintain order of successful uploads + expect(actualFiles).toEqual(['first.jpg', 'third.jpg']); + expect(Object.keys(uploadedFiles).length).toBe(totalFilesToUpload); + }); + }); +}); \ No newline at end of file