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 { getSource, SOURCES } from "../../sources/registry";
|
||||||
import { wrapStep, windowStart, resultsPanelOuter } from "../move";
|
import { wrapStep, windowStart, resultsPanelOuter } from "../move";
|
||||||
import { sortResults, nextSort, sortLabel, sortArrow, type Sort, type SortField } from "../sort";
|
import { sortResults, nextSort, sortLabel, sortArrow, type Sort, type SortField } from "../sort";
|
||||||
|
import { filterResults } from "../filter";
|
||||||
import { COLOR, GUTTER, ICON, sourceStyle } from "../theme";
|
import { COLOR, GUTTER, ICON, sourceStyle } from "../theme";
|
||||||
import { cleanText, formatBytes, formatCount, formatRelative, stripControl, truncate } from "../../util/format";
|
import { cleanText, formatBytes, formatCount, formatRelative, stripControl, truncate } from "../../util/format";
|
||||||
import type { Source, TorrentResult } from "../../sources/types";
|
import type { Source, TorrentResult } from "../../sources/types";
|
||||||
@@ -126,13 +127,14 @@ export function Results() {
|
|||||||
const search = useConcurrentSearch(query);
|
const search = useConcurrentSearch(query);
|
||||||
|
|
||||||
const [sort, setSort] = useState<Sort>("none");
|
const [sort, setSort] = useState<Sort>("none");
|
||||||
|
const [hideDead, setHideDead] = useState(false);
|
||||||
const results = useMemo(() => {
|
const results = useMemo(() => {
|
||||||
const cat = CATEGORIES.find((c) => c.key === section);
|
const cat = CATEGORIES.find((c) => c.key === section);
|
||||||
const base = cat?.group
|
const base = cat?.group
|
||||||
? search.results.filter((r) => getSource(r.source).group === cat.group)
|
? search.results.filter((r) => getSource(r.source).group === cat.group)
|
||||||
: search.results;
|
: search.results;
|
||||||
return sortResults(base, sort);
|
return sortResults(filterResults(base, hideDead), sort);
|
||||||
}, [search.results, section, sort]);
|
}, [search.results, section, sort, hideDead]);
|
||||||
|
|
||||||
const focused = region === "content";
|
const focused = region === "content";
|
||||||
const [mode, setMode] = useState<Mode>("list");
|
const [mode, setMode] = useState<Mode>("list");
|
||||||
@@ -213,6 +215,8 @@ export function Results() {
|
|||||||
if (r) copyResultMagnet(r);
|
if (r) copyResultMagnet(r);
|
||||||
} else if (input === "s") {
|
} else if (input === "s") {
|
||||||
setSort((cur) => nextSort(cur));
|
setSort((cur) => nextSort(cur));
|
||||||
|
} else if (input === "z") {
|
||||||
|
setHideDead((on) => !on);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ isActive: focused && mode === "list" },
|
{ isActive: focused && mode === "list" },
|
||||||
@@ -265,11 +269,12 @@ export function Results() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sortNote = sort === "none" ? "" : ` ${ICON.dot} sort: ${sortLabel(sort)}`;
|
const sortNote = sort === "none" ? "" : ` ${ICON.dot} sort: ${sortLabel(sort)}`;
|
||||||
|
const filterNote = hideDead ? ` ${ICON.dot} alive only` : "";
|
||||||
|
|
||||||
const status = () => {
|
const status = () => {
|
||||||
if (search.loading) {
|
if (search.loading) {
|
||||||
if (results.length > 0)
|
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 (
|
return (
|
||||||
<Spinner label={`${browsing ? "Loading" : "Searching"} ${search.done}/${search.total} sources`} />
|
<Spinner label={`${browsing ? "Loading" : "Searching"} ${search.done}/${search.total} sources`} />
|
||||||
);
|
);
|
||||||
@@ -292,6 +297,19 @@ export function Results() {
|
|||||||
</Text>
|
</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)
|
if (search.results.length > 0 && activeCat?.group)
|
||||||
return <Text dimColor>{`No ${activeCat.label.toLowerCase()} results yet. Try another tab or a search.`}</Text>;
|
return <Text dimColor>{`No ${activeCat.label.toLowerCase()} results yet. Try another tab or a search.`}</Text>;
|
||||||
return (
|
return (
|
||||||
@@ -304,7 +322,7 @@ export function Results() {
|
|||||||
const head = browsing
|
const head = browsing
|
||||||
? "newest across all sources"
|
? "newest across all sources"
|
||||||
: `${results.length} result${results.length === 1 ? "" : "s"}`;
|
: `${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 => {
|
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" },
|
||||||
{ keys: "D", label: "Download to a folder…" },
|
{ keys: "D", label: "Download to a folder…" },
|
||||||
{ keys: "s", label: "Sort results" },
|
{ keys: "s", label: "Sort results" },
|
||||||
|
{ keys: "z", label: "Hide dead torrents" },
|
||||||
{ keys: "y", label: "Copy magnet" },
|
{ keys: "y", label: "Copy magnet" },
|
||||||
{ keys: "m", label: "Paste magnet" },
|
{ keys: "m", label: "Paste magnet" },
|
||||||
],
|
],
|
||||||
@@ -113,6 +114,7 @@ export function footerHints(
|
|||||||
{ keys: "d/D", label: "Download" },
|
{ keys: "d/D", label: "Download" },
|
||||||
{ keys: "y", label: "Copy" },
|
{ keys: "y", label: "Copy" },
|
||||||
{ keys: "s", label: "Sort" },
|
{ keys: "s", label: "Sort" },
|
||||||
|
{ keys: "z", label: "Alive" },
|
||||||
{ keys: "/", label: "Search" },
|
{ keys: "/", label: "Search" },
|
||||||
SWITCH,
|
SWITCH,
|
||||||
ALWAYS,
|
ALWAYS,
|
||||||
|
|||||||
Reference in New Issue
Block a user