feat: enhance bulk duplicate finder with auto-selection and keyboard shortcuts

- Added functionality to auto-select assets based on criteria: lowest quality and smallest size.
- Implemented keyboard shortcut (Escape) to unselect all assets.
- Updated UI to include an auto-select dropdown in the floating bar for better user experience.
- Enhanced toast notifications to inform users about the auto-selection results.
This commit is contained in:
Varun Raj
2025-10-29 23:03:49 +05:30
parent ea1142801b
commit c0d51998b6
+101 -11
View File
@@ -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<string>();
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={
<div className="flex items-center gap-4">
{/* Selection Mode Button Group */}
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
@@ -426,18 +488,46 @@ export default function BulkDuplicatePage() {
</div>
)}
{selectionMode === 'discard' && selectedAssets.size > 0 && (
{selectionMode === 'discard' && (
<FloatingBar>
<div className="flex items-center gap-2 justify-between w-full">
<p className="text-sm text-muted-foreground">
{selectedAssetsInfo.count} Selected
{selectedAssetsInfo.totalSize > 0 && (
<span className="ml-2">
({humanizeBytes(selectedAssetsInfo.totalSize)})
</span>
)}
</p>
<div className="flex items-center gap-4 justify-between w-full">
<div className="flex items-center gap-4">
<p className="text-sm text-muted-foreground">
{selectedAssets.size > 0 ? (
<>
{selectedAssetsInfo.count} Selected
{selectedAssetsInfo.totalSize > 0 && (
<span className="ml-2">
({humanizeBytes(selectedAssetsInfo.totalSize)})
Savings
</span>
)}
</>
) : (
"No assets selected"
)}
</p>
{/* Auto-select Dropdown */}
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2">
<Select onValueChange={handleAutoSelect}>
<SelectTrigger className="w-40 h-8">
<SelectValue placeholder="Auto-select..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="lowest-quality">
Select lowest quality
</SelectItem>
<SelectItem value="smallest-size">
Select smallest size
</SelectItem>
</SelectContent>
</Select>
</div>
<AlertDialog
title="Delete the selected assets?"
description={`This action will delete ${selectedAssetsInfo.count} selected asset${selectedAssetsInfo.count !== 1 ? 's' : ''}${selectedAssetsInfo.totalSize > 0 ? ` and save ${humanizeBytes(selectedAssetsInfo.totalSize)} of storage` : ''}. This action cannot be undone.`}