mirror of
https://github.com/baairon/torlink.git
synced 2026-07-08 18:28:22 +02:00
feat: hide zero-seeder results with z key (#49)
This commit is contained in:
@@ -9,6 +9,7 @@ import { useConcurrentSearch } from "../hooks/useConcurrentSearch";
|
||||
import { getSource, SOURCES } from "../../sources/registry";
|
||||
import { wrapStep, windowStart, resultsPanelOuter } from "../move";
|
||||
import { sortResults, nextSort, sortLabel, sortArrow, type Sort, type SortField } from "../sort";
|
||||
import { filterResults } from "../filter";
|
||||
import { COLOR, GUTTER, ICON, sourceStyle } from "../theme";
|
||||
import { cleanText, formatBytes, formatCount, formatRelative, stripControl, truncate } from "../../util/format";
|
||||
import type { Source, TorrentResult } from "../../sources/types";
|
||||
@@ -126,13 +127,14 @@ export function Results() {
|
||||
const search = useConcurrentSearch(query);
|
||||
|
||||
const [sort, setSort] = useState<Sort>("none");
|
||||
const [hideDead, setHideDead] = useState(false);
|
||||
const results = useMemo(() => {
|
||||
const cat = CATEGORIES.find((c) => c.key === section);
|
||||
const base = cat?.group
|
||||
? search.results.filter((r) => getSource(r.source).group === cat.group)
|
||||
: search.results;
|
||||
return sortResults(base, sort);
|
||||
}, [search.results, section, sort]);
|
||||
return sortResults(filterResults(base, hideDead), sort);
|
||||
}, [search.results, section, sort, hideDead]);
|
||||
|
||||
const focused = region === "content";
|
||||
const [mode, setMode] = useState<Mode>("list");
|
||||
@@ -213,6 +215,8 @@ export function Results() {
|
||||
if (r) copyResultMagnet(r);
|
||||
} else if (input === "s") {
|
||||
setSort((cur) => nextSort(cur));
|
||||
} else if (input === "z") {
|
||||
setHideDead((on) => !on);
|
||||
}
|
||||
},
|
||||
{ isActive: focused && mode === "list" },
|
||||
@@ -265,11 +269,12 @@ export function Results() {
|
||||
};
|
||||
|
||||
const sortNote = sort === "none" ? "" : ` ${ICON.dot} sort: ${sortLabel(sort)}`;
|
||||
const filterNote = hideDead ? ` ${ICON.dot} alive only` : "";
|
||||
|
||||
const status = () => {
|
||||
if (search.loading) {
|
||||
if (results.length > 0)
|
||||
return <Text dimColor>{`searching… ${search.done}/${search.total} sources${sortNote}`}</Text>;
|
||||
return <Text dimColor>{`searching… ${search.done}/${search.total} sources${sortNote}${filterNote}`}</Text>;
|
||||
return (
|
||||
<Spinner label={`${browsing ? "Loading" : "Searching"} ${search.done}/${search.total} sources`} />
|
||||
);
|
||||
@@ -292,6 +297,19 @@ export function Results() {
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
if (hideDead) {
|
||||
const cat = CATEGORIES.find((c) => c.key === section);
|
||||
const base = cat?.group
|
||||
? search.results.filter((r) => getSource(r.source).group === cat.group)
|
||||
: search.results;
|
||||
if (base.length > 0 && base.every((r) => r.seeders <= 0)) {
|
||||
return (
|
||||
<Text dimColor>
|
||||
All results have zero seeders. Press z to show them.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (search.results.length > 0 && activeCat?.group)
|
||||
return <Text dimColor>{`No ${activeCat.label.toLowerCase()} results yet. Try another tab or a search.`}</Text>;
|
||||
return (
|
||||
@@ -304,7 +322,7 @@ export function Results() {
|
||||
const head = browsing
|
||||
? "newest across all sources"
|
||||
: `${results.length} result${results.length === 1 ? "" : "s"}`;
|
||||
return <Text dimColor>{`${head}${note}${sortNote}`}</Text>;
|
||||
return <Text dimColor>{`${head}${note}${sortNote}${filterNote}`}</Text>;
|
||||
};
|
||||
|
||||
const sortMark = (field: SortField, label: string): ReactNode => {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { filterResults } from "./filter";
|
||||
import type { SourceId, TorrentResult } from "../sources/types";
|
||||
|
||||
function r(p: Partial<TorrentResult> & { infoHash: string }): TorrentResult {
|
||||
return {
|
||||
name: p.name ?? p.infoHash,
|
||||
sizeBytes: p.sizeBytes ?? 0,
|
||||
seeders: p.seeders ?? 0,
|
||||
leechers: p.leechers ?? 0,
|
||||
source: (p.source ?? "yts") as SourceId,
|
||||
magnet: p.magnet ?? `magnet:?xt=urn:btih:${p.infoHash}`,
|
||||
...p,
|
||||
};
|
||||
}
|
||||
|
||||
describe("filterResults", () => {
|
||||
it("passes everything through when hideDead is off", () => {
|
||||
const list = [r({ infoHash: "a", seeders: 0 }), r({ infoHash: "b", seeders: 5 })];
|
||||
expect(filterResults(list, false)).toEqual(list);
|
||||
});
|
||||
|
||||
it("drops zero-seeder results when hideDead is on", () => {
|
||||
const list = [
|
||||
r({ infoHash: "a", seeders: 0 }),
|
||||
r({ infoHash: "b", seeders: 1 }),
|
||||
r({ infoHash: "c", seeders: 0 }),
|
||||
];
|
||||
expect(filterResults(list, true).map((x) => x.infoHash)).toEqual(["b"]);
|
||||
});
|
||||
|
||||
it("does not mutate the input array", () => {
|
||||
const list = [r({ infoHash: "a", seeders: 0 }), r({ infoHash: "b", seeders: 2 })];
|
||||
const before = list.map((x) => x.infoHash);
|
||||
filterResults(list, true);
|
||||
expect(list.map((x) => x.infoHash)).toEqual(before);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { TorrentResult } from "../sources/types";
|
||||
|
||||
export function filterResults(
|
||||
list: TorrentResult[],
|
||||
hideDead: boolean,
|
||||
): TorrentResult[] {
|
||||
if (!hideDead) return list;
|
||||
return list.filter((r) => r.seeders > 0);
|
||||
}
|
||||
@@ -31,6 +31,7 @@ export const HELP_GROUPS: HelpGroup[] = [
|
||||
{ keys: "d", label: "Download" },
|
||||
{ keys: "D", label: "Download to a folder…" },
|
||||
{ keys: "s", label: "Sort results" },
|
||||
{ keys: "z", label: "Hide dead torrents" },
|
||||
{ keys: "y", label: "Copy magnet" },
|
||||
{ keys: "m", label: "Paste magnet" },
|
||||
],
|
||||
@@ -113,6 +114,7 @@ export function footerHints(
|
||||
{ keys: "d/D", label: "Download" },
|
||||
{ keys: "y", label: "Copy" },
|
||||
{ keys: "s", label: "Sort" },
|
||||
{ keys: "z", label: "Alive" },
|
||||
{ keys: "/", label: "Search" },
|
||||
SWITCH,
|
||||
ALWAYS,
|
||||
|
||||
Reference in New Issue
Block a user