mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
# Description of Changes TLDR: - Added `naturalCompare` function to handle alphanumeric sorting - Updated `sortFiles` logic to use `naturalCompare` for filename sorting - Passed `naturalCompare` as dependency in sorting callback This pull request improves the file sorting logic in the `Merge` tool to provide a more natural, human-friendly ordering of filenames (e.g., "file2" now comes before "file10" instead of after). The main change is the introduction of a custom `naturalCompare` function that is used when sorting files by filename. File sorting improvements: * Added a `naturalCompare` function to sort filenames in a way that handles numeric portions naturally, ensuring files like "file2" are ordered before "file10" (`frontend/src/core/tools/Merge.tsx`). * Updated the file sorting logic to use `naturalCompare` instead of the default `localeCompare` when sorting by filename (`frontend/src/core/tools/Merge.tsx`). * Ensured the `sortFiles` callback properly depends on the new `naturalCompare` function (`frontend/src/core/tools/Merge.tsx`). Note: the sort on upload is natural sort (at least I think so I haven't checked the code), this is only relevant after upload, and you click the sort button again ### Before: <img width="1858" height="995" alt="image" src="https://github.com/user-attachments/assets/b6fe117e-5f70-4ff0-b16d-6a82a1ab5c2b" /> ### After: <img width="1858" height="995" alt="image" src="https://github.com/user-attachments/assets/4621950a-ce46-4f6e-b128-a0dd1d126973" /> <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [X] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [X] 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) - [X] I have performed a self-review of my own code - [X] 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) - [X] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [X] 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>
150 lines
4.7 KiB
TypeScript
150 lines
4.7 KiB
TypeScript
import { useCallback } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
|
|
import MergeSettings from "@app/components/tools/merge/MergeSettings";
|
|
import MergeFileSorter from "@app/components/tools/merge/MergeFileSorter";
|
|
import { useMergeParameters } from "@app/hooks/tools/merge/useMergeParameters";
|
|
import { useMergeOperation } from "@app/hooks/tools/merge/useMergeOperation";
|
|
import { useBaseTool } from "@app/hooks/tools/shared/useBaseTool";
|
|
import { BaseToolProps, ToolComponent } from "@app/types/tool";
|
|
import { useMergeTips } from "@app/components/tooltips/useMergeTips";
|
|
import { useFileManagement, useSelectedFiles, useAllFiles } from "@app/contexts/FileContext";
|
|
|
|
const Merge = (props: BaseToolProps) => {
|
|
const { t } = useTranslation();
|
|
const mergeTips = useMergeTips();
|
|
|
|
// File selection hooks for custom sorting
|
|
const { fileIds } = useAllFiles();
|
|
const { selectedFileStubs } = useSelectedFiles();
|
|
const { reorderFiles } = useFileManagement();
|
|
|
|
const base = useBaseTool(
|
|
'merge',
|
|
useMergeParameters,
|
|
useMergeOperation,
|
|
props,
|
|
{ minFiles: 2 }
|
|
);
|
|
const naturalCompare = useCallback((a: string, b: string): number => {
|
|
const isDigit = (char: string) => char >= '0' && char <= '9';
|
|
|
|
const getChunk = (s: string, length: number, marker: number): { chunk: string; newMarker: number } => {
|
|
let chunk = '';
|
|
const c = s.charAt(marker);
|
|
chunk += c;
|
|
marker++;
|
|
|
|
if (isDigit(c)) {
|
|
while (marker < length && isDigit(s.charAt(marker))) {
|
|
chunk += s.charAt(marker);
|
|
marker++;
|
|
}
|
|
} else {
|
|
while (marker < length && !isDigit(s.charAt(marker))) {
|
|
chunk += s.charAt(marker);
|
|
marker++;
|
|
}
|
|
}
|
|
return { chunk, newMarker: marker };
|
|
};
|
|
|
|
const len1 = a.length;
|
|
const len2 = b.length;
|
|
let marker1 = 0;
|
|
let marker2 = 0;
|
|
|
|
while (marker1 < len1 && marker2 < len2) {
|
|
const { chunk: chunk1, newMarker: newMarker1 } = getChunk(a, len1, marker1);
|
|
marker1 = newMarker1;
|
|
|
|
const { chunk: chunk2, newMarker: newMarker2 } = getChunk(b, len2, marker2);
|
|
marker2 = newMarker2;
|
|
|
|
let result: number;
|
|
if (isDigit(chunk1.charAt(0)) && isDigit(chunk2.charAt(0))) {
|
|
const num1 = parseInt(chunk1, 10);
|
|
const num2 = parseInt(chunk2, 10);
|
|
result = num1 - num2;
|
|
} else {
|
|
result = chunk1.localeCompare(chunk2);
|
|
}
|
|
|
|
if (result !== 0) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return len1 - len2;
|
|
}, []);
|
|
|
|
// Custom file sorting logic for merge tool
|
|
const sortFiles = useCallback((sortType: 'filename' | 'dateModified', ascending: boolean = true) => {
|
|
const sortedStubs = [...selectedFileStubs].sort((stubA, stubB) => {
|
|
let comparison = 0;
|
|
switch (sortType) {
|
|
case 'filename':
|
|
comparison = naturalCompare(stubA.name, stubB.name);
|
|
break;
|
|
case 'dateModified':
|
|
comparison = stubA.lastModified - stubB.lastModified;
|
|
break;
|
|
}
|
|
return ascending ? comparison : -comparison;
|
|
});
|
|
|
|
const selectedIds = sortedStubs.map(record => record.id);
|
|
const deselectedIds = fileIds.filter(id => !selectedIds.includes(id));
|
|
reorderFiles([...selectedIds, ...deselectedIds]);
|
|
}, [selectedFileStubs, fileIds, reorderFiles, naturalCompare]);
|
|
|
|
return createToolFlow({
|
|
files: {
|
|
selectedFiles: base.selectedFiles,
|
|
isCollapsed: base.hasResults,
|
|
minFiles: 2,
|
|
},
|
|
steps: [
|
|
{
|
|
title: "Sort Files",
|
|
isCollapsed: base.settingsCollapsed,
|
|
content: (
|
|
<MergeFileSorter
|
|
onSortFiles={sortFiles}
|
|
disabled={!base.hasFiles || base.endpointLoading}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: "Settings",
|
|
isCollapsed: base.settingsCollapsed,
|
|
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
|
|
tooltip: mergeTips,
|
|
content: (
|
|
<MergeSettings
|
|
parameters={base.params.parameters}
|
|
onParameterChange={base.params.updateParameter}
|
|
disabled={base.endpointLoading}
|
|
/>
|
|
),
|
|
},
|
|
],
|
|
executeButton: {
|
|
text: t("merge.submit", "Merge PDFs"),
|
|
isVisible: !base.hasResults,
|
|
loadingText: t("loading"),
|
|
onClick: base.handleExecute,
|
|
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
|
|
},
|
|
review: {
|
|
isVisible: base.hasResults,
|
|
operation: base.operation,
|
|
title: t("merge.title", "Merge Results"),
|
|
onFileClick: base.handleThumbnailClick,
|
|
onUndo: base.handleUndo,
|
|
},
|
|
});
|
|
};
|
|
|
|
export default Merge as ToolComponent;
|