diff --git a/src/components/TorrentList.tsx b/src/components/TorrentList.tsx index 8eff4e5..69196fe 100644 --- a/src/components/TorrentList.tsx +++ b/src/components/TorrentList.tsx @@ -94,6 +94,7 @@ export function TorrentList() { const [trackerFilter, setTrackerFilter] = useState(null) const [search, setSearch] = useState('') const [selected, setSelected] = useState>(new Set()) + const [lastSelected, setLastSelected] = useState(null) const [sortKey, setSortKey] = useState(() => { const stored = localStorage.getItem('sortKey') if (stored && COLUMNS.some((c) => c.sortKey === stored || stored === 'name')) return stored as SortKey @@ -380,17 +381,41 @@ export function TorrentList() { return filtered.slice(start, start + perPage) }, [filtered, page, perPage]) - function handleSelect(hash: string, multi: boolean) { - setSelected((prev) => { - if (multi) { - const next = new Set(prev) - if (next.has(hash)) next.delete(hash) - else next.add(hash) - return next + function handleSelect(hash: string, multi: boolean, range: boolean) { + if (range && lastSelected && filtered.some((t) => t.hash === lastSelected)) { + const idx1 = filtered.findIndex((t) => t.hash === lastSelected) + const idx2 = filtered.findIndex((t) => t.hash === hash) + if (idx1 !== -1 && idx2 !== -1) { + const start = Math.min(idx1, idx2) + const end = Math.max(idx1, idx2) + const rangeHashes = filtered.slice(start, end + 1).map((t) => t.hash) + setSelected((prev) => { + const next = new Set(prev) + rangeHashes.forEach((h) => next.add(h)) + return next + }) } - if (prev.has(hash) && prev.size === 1) return new Set() - return new Set([hash]) - }) + } else { + setSelected((prev) => { + if (multi) { + const next = new Set(prev) + if (next.has(hash)) next.delete(hash) + else next.add(hash) + return next + } + if (prev.has(hash) && prev.size === 1) return new Set() + return new Set([hash]) + }) + setLastSelected(hash) + } + } + + function handleSelectAll() { + if (selected.size === filtered.length && filtered.length > 0) { + setSelected(new Set()) + } else { + setSelected(new Set(filtered.map((t) => t.hash))) + } } useEffect(() => { @@ -584,14 +609,38 @@ export function TorrentList() { className="px-4 py-1.5 text-left relative" style={columnWidths.name ? { width: columnWidths.name } : undefined} > - +
+ + +
handleResizeStart(e, 'name')} /> {orderedColumns diff --git a/src/components/TorrentRow.tsx b/src/components/TorrentRow.tsx index 91a26fe..3b52a36 100644 --- a/src/components/TorrentRow.tsx +++ b/src/components/TorrentRow.tsx @@ -273,7 +273,7 @@ function renderCell(columnId: string, torrent: Torrent, ctx: CellContext): React interface Props { torrent: Torrent selected: boolean - onSelect: (hash: string, multi: boolean) => void + onSelect: (hash: string, multi: boolean, range: boolean) => void onContextMenu: (e: React.MouseEvent) => void ratioThreshold: number hideAddedTime: boolean @@ -307,7 +307,8 @@ export function TorrentRow({ return ( onSelect(torrent.hash, e.ctrlKey || e.metaKey)} + onMouseDown={(e) => { if (e.shiftKey) e.preventDefault() }} + onClick={(e) => onSelect(torrent.hash, e.ctrlKey || e.metaKey, e.shiftKey)} onContextMenu={onContextMenu} className={`group cursor-pointer transition-colors duration-150 ${isDownloading ? 'downloading' : ''}`} style={{