All files / src/components/downloads RetentionNote.tsx

100% Statements 21/21
96.66% Branches 29/30
100% Functions 3/3
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82                                            10x 154x 154x 154x 154x   17x 17x 17x 154x   154x 6x 6x 8x 6x 1x           154x 8x 8x 8x                         9x                                          
// SPDX-License-Identifier: GPL-3.0-only
// Copyright (C) 2026 Max Boksem. See NOTICE for additional terms under GPLv3 section 7.
 
import { Clock, Pin } from "lucide-react";
import { useDownloads } from "@/hooks/useDownloads";
import { useNow } from "@/hooks/useNow";
import { daysUntil, deleteLabel, summarize } from "@/lib/retention";
import { setDownloadKeep } from "@/lib/tauri";
import type { DownloadRecord } from "@/lib/types";
 
interface RetentionNoteProps {
	/** One download, or every episode download of a series. */
	records: DownloadRecord[];
	/** An icon and day count for tight rows, with the full text as its tooltip. */
	compact?: boolean;
	className?: string;
}
 
/**
 * Says when the "Delete downloads after" setting deletes these downloads, with
 * a button to keep them. Renders nothing while the setting is off.
 */
export const RetentionNote = ({ records, compact = false, className = "" }: RetentionNoteProps) => {
	const { retentionDays } = useDownloads();
	const now = useNow();
	const summary = summarize(records, retentionDays);
	if (summary.state === "none") return null;
 
	const kept = summary.state === "kept";
	const many = summary.count > 1;
	const label = kept ? (many ? "All kept" : "Kept") : deleteLabel(summary.at, now, many);
	const action = kept ? "Don't keep" : many ? "Keep all" : "Keep";
 
	const toggle = (e: React.MouseEvent) => {
		e.stopPropagation();
		for (const r of records) {
			if (r.status === "completed" && r.keep === kept) {
				setDownloadKeep(r.id, !kept).catch((err) =>
					console.error("[Downloads] failed to change Keep:", err)
				);
			}
		}
	};
 
	if (compact) {
		const days = kept ? null : daysUntil(summary.at, now);
		const hint = kept ? "Click to stop keeping." : `Click to ${action.toLowerCase()}.`;
		return (
			<button
				onClick={toggle}
				title={`${label}. ${hint}`}
				aria-label={`${label}. ${action}`}
				className={`flex items-center gap-0.5 text-[10px] tabular-nums text-muted-foreground hover:text-foreground ${className}`}
			>
				{kept ? <Pin className="h-3 w-3" /> : <Clock className="h-3 w-3" />}
				{days !== null && (days > 0 ? `${days}d` : "soon")}
			</button>
		);
	}
 
	return (
		<div
			className={`flex items-center justify-between gap-2 text-xs text-muted-foreground ${className}`}
		>
			<span className="flex items-center gap-1 truncate">
				{kept ? (
					<Pin className="h-3 w-3 shrink-0" />
				) : (
					<Clock className="h-3 w-3 shrink-0" />
				)}
				{label}
			</span>
			<button
				onClick={toggle}
				className="shrink-0 font-medium text-foreground hover:underline"
			>
				{action}
			</button>
		</div>
	);
};