From e0fe1756c61710637a674d4d8270db742f29f11f Mon Sep 17 00:00:00 2001 From: medy17 <88668010+medy17@users.noreply.github.com> Date: Sat, 7 Feb 2026 06:21:39 +0800 Subject: [PATCH 1/2] added shift+click on torrent list --- src/App.tsx | 4 +- src/components/TorrentList.tsx | 85 +++++++++++++++++++++++++++------- src/components/TorrentRow.tsx | 4 +- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index bb46eb5..e252ac5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ import { useState, useEffect, useCallback, lazy, Suspense } from 'react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ThemeProvider } from './contexts/ThemeProvider' -import { InstanceProvider } from './contexts/InstanceContext' -import { PaginationProvider } from './contexts/PaginationContext' +import { InstanceProvider } from './contexts/InstanceProvider' +import { PaginationProvider } from './contexts/PaginationProvider' import { Layout } from './components/Layout' import { AuthForm } from './components/AuthForm' import { InstanceManager } from './components/InstanceManager' 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..5dab286 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,7 @@ export function TorrentRow({ return ( onSelect(torrent.hash, e.ctrlKey || e.metaKey)} + onClick={(e) => onSelect(torrent.hash, e.ctrlKey || e.metaKey, e.shiftKey)} onContextMenu={onContextMenu} className={`group cursor-pointer transition-colors duration-150 ${isDownloading ? 'downloading' : ''}`} style={{ From 9ce2236cae6681701559603e2b9fa5dcad38f772 Mon Sep 17 00:00:00 2001 From: medy17 <88668010+medy17@users.noreply.github.com> Date: Sat, 7 Feb 2026 06:31:52 +0800 Subject: [PATCH 2/2] added onMouseDown handling for Shift+Click to prevent text selection on multi-row selections --- src/App.tsx | 4 ++-- src/components/TorrentRow.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index e252ac5..bb46eb5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ import { useState, useEffect, useCallback, lazy, Suspense } from 'react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ThemeProvider } from './contexts/ThemeProvider' -import { InstanceProvider } from './contexts/InstanceProvider' -import { PaginationProvider } from './contexts/PaginationProvider' +import { InstanceProvider } from './contexts/InstanceContext' +import { PaginationProvider } from './contexts/PaginationContext' import { Layout } from './components/Layout' import { AuthForm } from './components/AuthForm' import { InstanceManager } from './components/InstanceManager' diff --git a/src/components/TorrentRow.tsx b/src/components/TorrentRow.tsx index 5dab286..3b52a36 100644 --- a/src/components/TorrentRow.tsx +++ b/src/components/TorrentRow.tsx @@ -307,6 +307,7 @@ export function TorrentRow({ return ( { 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' : ''}`}