diff --git a/src/pages/assets/bulk-duplicate-finder.tsx b/src/pages/assets/bulk-duplicate-finder.tsx index 5cb981b..9f59797 100644 --- a/src/pages/assets/bulk-duplicate-finder.tsx +++ b/src/pages/assets/bulk-duplicate-finder.tsx @@ -6,9 +6,10 @@ import Header from '@/components/shared/Header' import VirtualizedDuplicateList from '@/components/assets/duplicate-assets/VirtualizedDuplicateList' import Loader from '@/components/ui/loader' import { Button } from '@/components/ui/button' -import { RefreshCw, Search, Shield, Trash2 } from 'lucide-react' +import { RefreshCw, Search, Shield, Trash2, Zap } from 'lucide-react' import FloatingBar from '@/components/shared/FloatingBar' import { AlertDialog } from '@/components/ui/alert-dialog' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { toast } from '@/components/ui/use-toast' import { humanizeBytes } from '@/helpers/string.helper' @@ -69,6 +70,22 @@ export default function BulkDuplicatePage() { }; }, [duplicates]); // Re-calculate when duplicates change + // Handle Escape key to unselect all + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape' && selectedAssets.size > 0) { + setSelectedAssets(new Set()); + setLastSelectedIndex(-1); + } + }; + + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [selectedAssets.size]); + // Flatten all asset IDs for selection operations const allAssetIds = useMemo(() => { return duplicates.flatMap(record => record.assets.map(asset => asset.id)); @@ -198,6 +215,50 @@ export default function BulkDuplicatePage() { } }, [duplicates]); + const handleAutoSelect = (criteria: 'lowest-quality' | 'smallest-size') => { + const newSelection = new Set(); + + duplicates.forEach(record => { + if (record.assets.length <= 1) return; // Skip if only one asset + + let selectedAsset = record.assets[0]; + + for (let i = 1; i < record.assets.length; i++) { + const currentAsset = record.assets[i]; + + if (criteria === 'lowest-quality') { + // Compare by resolution (width * height) - select lowest + const currentResolution = currentAsset.exifInfo.exifImageWidth * currentAsset.exifInfo.exifImageHeight; + const selectedResolution = selectedAsset.exifInfo.exifImageWidth * selectedAsset.exifInfo.exifImageHeight; + + if (currentResolution < selectedResolution) { + selectedAsset = currentAsset; + } + } else if (criteria === 'smallest-size') { + // Compare by file size - select smallest + if (currentAsset.exifInfo.fileSizeInByte < selectedAsset.exifInfo.fileSizeInByte) { + selectedAsset = currentAsset; + } + } + } + + newSelection.add(selectedAsset.id); + }); + + setSelectedAssets(newSelection); + setLastSelectedIndex(-1); + + const criteriaText = { + 'lowest-quality': 'lowest quality', + 'smallest-size': 'smallest size' + }[criteria]; + + toast({ + title: "Auto-selection complete", + description: `Selected ${newSelection.size} assets based on ${criteriaText} criteria.`, + }); + }; + const handleDeleteAllSelected = async () => { if (selectedAssets.size === 0 || selectionMode !== 'discard') return; @@ -292,6 +353,7 @@ export default function BulkDuplicatePage() { leftComponent="Bulk Duplicate Finder" rightComponent={
+ {/* Selection Mode Button Group */}
@@ -426,18 +488,46 @@ export default function BulkDuplicatePage() {
)} - {selectionMode === 'discard' && selectedAssets.size > 0 && ( + {selectionMode === 'discard' && ( -
-

- {selectedAssetsInfo.count} Selected - {selectedAssetsInfo.totalSize > 0 && ( - - ({humanizeBytes(selectedAssetsInfo.totalSize)}) - - )} -

+
+
+

+ {selectedAssets.size > 0 ? ( + <> + {selectedAssetsInfo.count} Selected + {selectedAssetsInfo.totalSize > 0 && ( + + ({humanizeBytes(selectedAssetsInfo.totalSize)}) + Savings + + )} + + ) : ( + "No assets selected" + )} +

+ + {/* Auto-select Dropdown */} + +
+
+
+ +
0 ? ` and save ${humanizeBytes(selectedAssetsInfo.totalSize)} of storage` : ''}. This action cannot be undone.`}