feat(sort): enhance file sorting and order handling (#4813)

# Description of Changes

- Added async support to sorting functions for improved UI
responsiveness in merge.js.
- Enhanced logging for debugging file orders in both JavaScript and
Java.
- Improved file order validation and handling in the backend, ensuring
consistent sorting and upload order.
- Refactored file order processing with better trimming, handling for
empty entries, and logging unmatched filenames.

Closes: #4810


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
This commit is contained in:
Balázs Szücs 2025-11-07 14:35:18 +01:00 committed by GitHub
parent 2acb3aa6e5
commit abd1ae1bf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 46 additions and 18 deletions

View File

@ -72,19 +72,29 @@ public class MergeController {
// fileOrder is newline-delimited original filenames in the desired order.
private static MultipartFile[] reorderFilesByProvidedOrder(
MultipartFile[] files, String fileOrder) {
String[] desired = fileOrder.split("\n", -1);
// Split by various line endings and trim each entry
String[] desired =
stirling.software.common.util.RegexPatternUtils.getInstance()
.getNewlineSplitPattern()
.split(fileOrder);
List<MultipartFile> remaining = new ArrayList<>(Arrays.asList(files));
List<MultipartFile> ordered = new ArrayList<>(files.length);
for (String name : desired) {
if (name == null || name.isEmpty()) continue;
name = name.trim();
if (name.isEmpty()) {
log.debug("Skipping empty entry");
continue;
}
int idx = indexOfByOriginalFilename(remaining, name);
if (idx >= 0) {
ordered.add(remaining.remove(idx));
} else {
log.debug("Filename from order list not found in uploaded files: {}", name);
}
}
// Append any files not explicitly listed, preserving their relative order
ordered.addAll(remaining);
return ordered.toArray(new MultipartFile[0]);
}
@ -252,8 +262,10 @@ public class MergeController {
// If front-end provided explicit visible order, honor it and override backend sorting
if (fileOrder != null && !fileOrder.isBlank()) {
log.info("Reordering files based on fileOrder parameter");
files = reorderFilesByProvidedOrder(files, fileOrder);
} else {
log.info("Sorting files based on sortType: {}", request.getSortType());
Arrays.sort(
files,
getSortComparator(

View File

@ -74,6 +74,12 @@
showGameBtn.style.display = 'none';
}
// Log fileOrder for debugging
const fileOrderValue = formData.get('fileOrder');
if (fileOrderValue) {
console.log('FormData fileOrder:', fileOrderValue);
}
// Remove empty file entries
for (let [key, value] of formData.entries()) {
if (value instanceof File && !value.name) {

View File

@ -123,39 +123,38 @@ function attachMoveButtons() {
}
}
document.getElementById("sortByNameBtn").addEventListener("click", function () {
document.getElementById("sortByNameBtn").addEventListener("click", async function () {
if (currentSort.field === "name" && !currentSort.descending) {
currentSort.descending = true;
sortFiles((a, b) => b.name.localeCompare(a.name));
await sortFiles((a, b) => b.name.localeCompare(a.name));
} else {
currentSort.field = "name";
currentSort.descending = false;
sortFiles((a, b) => a.name.localeCompare(b.name));
await sortFiles((a, b) => a.name.localeCompare(b.name));
}
});
document.getElementById("sortByDateBtn").addEventListener("click", function () {
document.getElementById("sortByDateBtn").addEventListener("click", async function () {
if (currentSort.field === "lastModified" && !currentSort.descending) {
currentSort.descending = true;
sortFiles((a, b) => b.lastModified - a.lastModified);
await sortFiles((a, b) => b.lastModified - a.lastModified);
} else {
currentSort.field = "lastModified";
currentSort.descending = false;
sortFiles((a, b) => a.lastModified - b.lastModified);
await sortFiles((a, b) => a.lastModified - b.lastModified);
}
});
function sortFiles(comparator) {
async function sortFiles(comparator) {
// Convert FileList to array and sort
const sortedFilesArray = Array.from(document.getElementById("fileInput-input").files).sort(comparator);
// Refresh displayed list
displayFiles(sortedFilesArray);
// Refresh displayed list (wait for it to complete since it's async)
await displayFiles(sortedFilesArray);
// Update the files property
const dataTransfer = new DataTransfer();
sortedFilesArray.forEach((file) => dataTransfer.items.add(file));
document.getElementById("fileInput-input").files = dataTransfer.files;
// Update the file input and fileOrder based on the current display order
// This ensures consistency between display and file input
updateFiles();
}
function updateFiles() {
@ -163,25 +162,36 @@ function updateFiles() {
var liElements = document.querySelectorAll("#selectedFiles li");
const files = document.getElementById("fileInput-input").files;
console.log("updateFiles: found", liElements.length, "LI elements and", files.length, "files");
for (var i = 0; i < liElements.length; i++) {
var fileNameFromList = liElements[i].querySelector(".filename").innerText;
var fileFromFiles;
var found = false;
for (var j = 0; j < files.length; j++) {
var file = files[j];
if (file.name === fileNameFromList) {
dataTransfer.items.add(file);
found = true;
break;
}
}
if (!found) {
console.warn("updateFiles: Could not find file:", fileNameFromList);
}
}
document.getElementById("fileInput-input").files = dataTransfer.files;
console.log("updateFiles: Updated file input with", dataTransfer.files.length, "files");
// Also populate hidden fileOrder to preserve visible order
const order = Array.from(liElements)
.map((li) => li.querySelector(".filename").innerText)
.join("\n");
const orderInput = document.getElementById("fileOrder");
if (orderInput) orderInput.value = order;
if (orderInput) {
orderInput.value = order;
console.log("Updated fileOrder:", order);
}
}
document.querySelector("#resetFileInputBtn").addEventListener("click", ()=>{