From 4688c941e8e9b0450b61f04c24e932a0d3d6c0ed Mon Sep 17 00:00:00 2001 From: NimBold Date: Mon, 29 Jun 2026 10:22:29 +0330 Subject: [PATCH] fix(ui): keep table header visible when list is empty, improve multiselect standard OS behaviors, and fix multi-select context menu actions --- src/components/DownloadTable.tsx | 215 ++++++++++++++++++------------- 1 file changed, 127 insertions(+), 88 deletions(-) diff --git a/src/components/DownloadTable.tsx b/src/components/DownloadTable.tsx index ddfa421..23362bc 100644 --- a/src/components/DownloadTable.tsx +++ b/src/components/DownloadTable.tsx @@ -96,10 +96,19 @@ export const DownloadTable: React.FC = ({ filter }) => { const activeEl = document.activeElement as HTMLElement | null; const isInput = activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA' || activeEl.isContentEditable); - if (!isInput && (e.key === 'Delete' || e.key === 'Backspace')) { - if (!activeEl || !activeEl.closest('.sidebar-inner')) { - if (selectedIds.size > 0) { - handleDelete(Array.from(selectedIds)); + if (!isInput) { + if ((e.key === 'a' || e.key === 'A') && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + const allIds = sortedDownloads.map(d => d.id); + setSelectedIds(new Set(allIds)); + return; + } + + if (e.key === 'Delete' || e.key === 'Backspace') { + if (!activeEl || !activeEl.closest('.sidebar-inner')) { + if (selectedIds.size > 0) { + handleDelete(Array.from(selectedIds)); + } } } } @@ -255,7 +264,21 @@ export const DownloadTable: React.FC = ({ filter }) => { handleDownloadDoubleClick(item); return; } - if (e.metaKey || e.ctrlKey) { + if (e.shiftKey && lastSelectedId) { + const currentIndex = sortedDownloads.findIndex(d => d.id === item.id); + const lastIndex = sortedDownloads.findIndex(d => d.id === lastSelectedId); + + if (currentIndex !== -1 && lastIndex !== -1) { + const start = Math.min(currentIndex, lastIndex); + const end = Math.max(currentIndex, lastIndex); + + const newSelected = (e.metaKey || e.ctrlKey) ? new Set(selectedIds) : new Set(); + for (let i = start; i <= end; i++) { + newSelected.add(sortedDownloads[i].id); + } + setSelectedIds(newSelected); + } + } else if (e.metaKey || e.ctrlKey) { const newSelected = new Set(selectedIds); if (newSelected.has(item.id)) { newSelected.delete(item.id); @@ -264,20 +287,6 @@ export const DownloadTable: React.FC = ({ filter }) => { } setSelectedIds(newSelected); setLastSelectedId(item.id); - } else if (e.shiftKey && lastSelectedId) { - const currentIndex = sortedDownloads.findIndex(d => d.id === item.id); - const lastIndex = sortedDownloads.findIndex(d => d.id === lastSelectedId); - - if (currentIndex !== -1 && lastIndex !== -1) { - const start = Math.min(currentIndex, lastIndex); - const end = Math.max(currentIndex, lastIndex); - - const newSelected = new Set(selectedIds); - for (let i = start; i <= end; i++) { - newSelected.add(sortedDownloads[i].id); - } - setSelectedIds(newSelected); - } } else { setSelectedIds(new Set([item.id])); setLastSelectedId(item.id); @@ -426,70 +435,68 @@ export const DownloadTable: React.FC = ({ filter }) => {
- {sortedDownloads.length === 0 ? ( -
-
- ) : ( - <> -
-
- {['File Name', 'Size', 'Status', 'Speed', 'ETA', 'Date Added'].map((label, index) => ( -
handleSort(label)} - > -
0 ? 'justify-center' : ''}`}> - {label} - {sortConfig?.column === label && ( - sortConfig.direction === 'asc' ? : - )} -
-
startColumnResize(index, event)} - /> +
+
+ {['File Name', 'Size', 'Status', 'Speed', 'ETA', 'Date Added'].map((label, index) => ( +
handleSort(label)} + > +
0 ? 'justify-center' : ''}`}> + {label} + {sortConfig?.column === label && ( + sortConfig.direction === 'asc' ? : + )}
- ))} -
- -
-
- {sortedDownloads.map((d, index) => ( - startColumnResize(index, event)} /> - ))} -
-
-
-
- - )} +
+ ))} +
+ +
+ {sortedDownloads.length === 0 ? ( +
+
+ ) : ( +
+ {sortedDownloads.map((d, index) => ( + + ))} +
+
+ )} +
+
{/* Floating Context Menu */} @@ -503,16 +510,21 @@ export const DownloadTable: React.FC = ({ filter }) => { }} onClick={(e) => e.stopPropagation()} > - {selectedIds.size > 1 ? ( + {selectedIds.size > 1 ? (() => { + const selectedDownloads = Array.from(selectedIds) + .map(id => downloads.find(d => d.id === id)) + .filter((item): item is DownloadItem => !!item); + + const itemsToResume = selectedDownloads.filter(d => canStartDownload(d.status)); + const itemsToPause = selectedDownloads.filter(d => canPauseDownload(d.status)); + + return ( <> {/* Multi-Select Context Menu */} + {itemsToResume.length > 0 && ( + )} -
+ {itemsToPause.length > 0 && ( + + )} + + {(itemsToResume.length > 0 || itemsToPause.length > 0) && ( +
+ )}
- ) : ( + ); + })() : ( <> {/* Single-Select Context Menu */} {contextItem.status === 'completed' && (