From 1fd4a18d17016b2511e8ececbd5ab0aed766d6a1 Mon Sep 17 00:00:00 2001 From: NimBold Date: Sun, 28 Jun 2026 15:01:15 +0330 Subject: [PATCH] feat(ui): add interactive column sorting and optimize bulk start operations in main window --- src/components/DownloadTable.tsx | 92 +++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/src/components/DownloadTable.tsx b/src/components/DownloadTable.tsx index b078483..be8ed1a 100644 --- a/src/components/DownloadTable.tsx +++ b/src/components/DownloadTable.tsx @@ -3,7 +3,7 @@ import { useDownloadStore, DownloadItem } from '../store/useDownloadStore'; import { useToast } from '../contexts/ToastContext'; import { useSettingsStore } from '../store/useSettingsStore'; import { SidebarFilter } from './Sidebar'; -import { Play, Pause, Plus, FileText, Image as ImageIcon, Music, Film, Box, Archive, FileQuestion, PanelLeft, ArrowDownCircle, Command, ChevronRight } from 'lucide-react'; +import { Play, Pause, Plus, FileText, Image as ImageIcon, Music, Film, Box, Archive, FileQuestion, PanelLeft, ArrowDownCircle, Command, ChevronRight, ChevronUp, ChevronDown } from 'lucide-react'; import { DownloadItem as DownloadItemComponent } from './DownloadItem'; import { invokeCommand as invoke } from '../ipc'; import { @@ -35,6 +35,7 @@ export const DownloadTable: React.FC = ({ filter }) => { const [contextMenu, setContextMenu] = useState<{ x: number; y: number; id: string } | null>(null); const [selectedIds, setSelectedIds] = useState>(new Set()); const [lastSelectedId, setLastSelectedId] = useState(null); + const [sortConfig, setSortConfig] = useState<{ column: string; direction: 'asc' | 'desc' } | null>(null); const [columnWidths, setColumnWidths] = useState(() => { try { const stored = JSON.parse(window.localStorage.getItem(COLUMN_WIDTHS_STORAGE_KEY) || 'null'); @@ -184,6 +185,27 @@ export const DownloadTable: React.FC = ({ filter }) => { } }); + const parseSpeed = (speedStr?: string) => { + if (!speedStr || speedStr === '-') return 0; + const val = parseFloat(speedStr); + if (speedStr.includes('KB/s')) return val * 1024; + if (speedStr.includes('MB/s')) return val * 1024 * 1024; + if (speedStr.includes('GB/s')) return val * 1024 * 1024 * 1024; + return val; + }; + + const parseEta = (etaStr?: string) => { + if (!etaStr || etaStr === '-') return Infinity; + let seconds = 0; + const hours = etaStr.match(/(\d+)h/); + const minutes = etaStr.match(/(\d+)m/); + const secs = etaStr.match(/(\d+)s/); + if (hours) seconds += parseInt(hours[1]) * 3600; + if (minutes) seconds += parseInt(minutes[1]) * 60; + if (secs) seconds += parseInt(secs[1]); + return seconds; + }; + // Sort by queue position when viewing a specific queue so the visual // order matches the queue order and move-up/down buttons reflect reality. const sortedDownloads = filter.startsWith('queue:') @@ -196,7 +218,32 @@ export const DownloadTable: React.FC = ({ filter }) => { return (left.queuePosition ?? 0) - (right.queuePosition ?? 0); }) - : filteredDownloads; + : sortConfig + ? [...filteredDownloads].sort((a, b) => { + let comparison = 0; + switch (sortConfig.column) { + case 'File Name': + comparison = (a.fileName || a.url || '').localeCompare(b.fileName || b.url || ''); + break; + case 'Size': + comparison = parseInt(a.size || '0', 10) - parseInt(b.size || '0', 10); + break; + case 'Status': + comparison = a.status.localeCompare(b.status); + break; + case 'Speed': + comparison = parseSpeed(a.speed) - parseSpeed(b.speed); + break; + case 'ETA': + comparison = parseEta(a.eta) - parseEta(b.eta); + break; + case 'Date Added': + comparison = new Date(a.dateAdded || 0).getTime() - new Date(b.dateAdded || 0).getTime(); + break; + } + return sortConfig.direction === 'asc' ? comparison : -comparison; + }) + : filteredDownloads; const handleItemClick = (e: React.MouseEvent, item: DownloadItem) => { if (e.detail === 2) { handleDownloadDoubleClick(item); @@ -239,6 +286,17 @@ export const DownloadTable: React.FC = ({ filter }) => { setContextMenu(menu); }; + const handleSort = (column: string) => { + if (filter.startsWith('queue:')) return; // Disable custom sorting in queues + setSortConfig(current => { + if (current?.column === column) { + if (current.direction === 'desc') return null; // Reset sort + return { column, direction: 'desc' }; + } + return { column, direction: 'asc' }; + }); + }; + const getFilterTitle = () => { if (filter.startsWith('queue:')) { @@ -386,7 +444,15 @@ export const DownloadTable: React.FC = ({ filter }) => {
{['File Name', 'Size', 'Status', 'Speed', 'ETA', 'Date Added'].map((label, index) => (
- {label} +
handleSort(label)} + > + {label} + {sortConfig?.column === label && ( + sortConfig.direction === 'asc' ? : + )} +
startColumnResize(index, event)} @@ -436,12 +502,22 @@ export const DownloadTable: React.FC = ({ filter }) => {