All files / src/components/channels DownloadsTab.tsx

100% Statements 20/20
78.94% Branches 15/19
100% Functions 4/4
100% Lines 16/16

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                      5x             5x 23x 23x 28x   23x     23x 28x   23x   99x   23x 3x             20x                     25x 25x 25x                                  
// SPDX-License-Identifier: GPL-3.0-only
// Copyright (C) 2026 Max Boksem. See NOTICE for additional terms under GPLv3 section 7.
 
import { useChannels } from "@/hooks/useChannels";
import { useDownloads } from "@/hooks/useDownloads";
import { ChannelCard } from "./ChannelCard";
import { RetentionNote } from "@/components/downloads/RetentionNote";
import { RecordingsSection } from "@/components/downloads/RecordingsSection";
import { isMobilePlatform } from "@/lib/platform";
import type { Channel } from "@/lib/types";
 
const RECORDING_SUPPORTED = !isMobilePlatform();
 
interface DownloadsTabProps {
	onPlay: (channel: Channel) => void;
	onToggleFavorite?: (channel: Channel) => void;
}
 
export const DownloadsTab = ({ onPlay, onToggleFavorite }: DownloadsTabProps) => {
	const { channels } = useChannels();
	const { downloads, byChannel, bySeries } = useDownloads();
	const recordings = RECORDING_SUPPORTED ? downloads.filter((d) => d.kind === "recording") : [];
 
	const ids = new Set<string>();
	// Standalone cards only for movie downloads — individual episodes are
	// represented by their parent series card (added from bySeries below).
	for (const [channelId, rec] of byChannel) {
		if (rec.kind === "movie") ids.add(channelId);
	}
	for (const [seriesId] of bySeries) ids.add(seriesId);
 
	const items = channels.filter((c) => ids.has(c.id));
 
	if (items.length === 0 && recordings.length === 0) {
		return (
			<div className="flex items-center justify-center h-40 text-sm text-muted-foreground">
				No downloads yet.
			</div>
		);
	}
 
	return (
		<>
			{recordings.length > 0 && <RecordingsSection recordings={recordings} onPlay={onPlay} />}
			{items.length > 0 && recordings.length > 0 && (
				<h2 className="px-4 pt-5 -mb-2 text-[10px] font-semibold tracking-widest text-muted-foreground uppercase">
					Movies and series
				</h2>
			)}
			{items.length > 0 && (
				<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3 p-4">
					{items.map((c) => {
						const movie = byChannel.get(c.id);
						const records = bySeries.get(c.id) ?? (movie ? [movie] : []);
						return (
							<div key={c.id} className="flex flex-col gap-1.5">
								<ChannelCard
									channel={c}
									variant="poster"
									onPlay={onPlay}
									onToggleFavorite={onToggleFavorite}
								/>
								<RetentionNote records={records} />
							</div>
						);
					})}
				</div>
			)}
		</>
	);
};