import React, { useEffect, useRef } from 'react'; import { useDownloadStore } from '../store/useDownloadStore'; import { useDownloadProgressStore } from '../store/downloadStore'; import { Play, Pause, MoreVertical, Clock, ArrowUp, ArrowDown } from 'lucide-react'; import type { DownloadItem as DownloadItemType } from '../bindings/DownloadItem'; import { canPauseDownload, canStartDownload, startActionLabel } from '../utils/downloadActions'; interface DownloadItemProps { downloadId: string; index: number; tableGridTemplate: string; setContextMenu: (menu: { x: number; y: number; id: string }) => void; handlePause: (id: string) => void; handleResume: (item: DownloadItemType) => void; handleDoubleClick: (item: DownloadItemType) => void; getCategoryIcon: (category: string) => React.ReactNode; isSelected: boolean; onClick: (e: React.MouseEvent, item: DownloadItemType) => void; } export const DownloadItem = React.memo(({ downloadId, index, tableGridTemplate, setContextMenu, handlePause, handleResume, handleDoubleClick, getCategoryIcon, isSelected, onClick, }) => { const download = useDownloadStore(state => state.downloads.find(d => d.id === downloadId)); const pendingOrder = useDownloadStore(state => state.pendingOrder); const moveInQueue = useDownloadStore(state => state.moveInQueue); const queueIndex = pendingOrder.indexOf(downloadId); const progressBarRef = useRef(null); const statusTextRef = useRef(null); const speedTextRef = useRef(null); const etaTextRef = useRef(null); useEffect(() => { if (!download || download.status !== 'downloading') return; const unsubscribe = useDownloadProgressStore.subscribe((state) => { const progress = state.progressMap[downloadId]; if (!progress) return; if (progressBarRef.current) { progressBarRef.current.style.width = `${progress.fraction * 100}%`; } if (statusTextRef.current) { statusTextRef.current.innerText = `${(progress.fraction * 100).toFixed(0)}%`; statusTextRef.current.title = `${(progress.fraction * 100).toFixed(0)}%`; } if (speedTextRef.current) { speedTextRef.current.innerText = progress.speed; speedTextRef.current.title = progress.speed; } if (etaTextRef.current) { etaTextRef.current.innerText = progress.eta; etaTextRef.current.title = progress.eta; } }); return () => unsubscribe(); }, [downloadId, download?.status]); if (!download) return null; return (
onClick(e, download)} onContextMenu={(e) => { e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY, id: download.id }); }} onDoubleClick={() => handleDoubleClick(download)} >
{getCategoryIcon(download.category)} {download.fileName}
{download.size && download.size !== '-' ? download.size : 'Unknown'}
{download.status === 'completed' ? ( Completed ) : ( <>
{download.status === 'queued' && queueIndex !== -1 ? ( <> Queued #{queueIndex + 1} ) : download.status === 'downloading' ? ( `${((download.fraction || 0) * 100).toFixed(0)}%` ) : download.status === 'processing' ? ( 'Processing' ) : ( download.status.charAt(0).toUpperCase() + download.status.slice(1) )} )}
{download.status === 'downloading' ? download.speed : download.status === 'processing' ? 'Processing…' : '-'}
{download.status === 'downloading' ? download.eta : download.status === 'processing' ? 'Muxing…' : '-'}
{download.dateAdded ? new Date(download.dateAdded).toLocaleDateString() : '-'}
e.stopPropagation()} > {download.status === 'queued' && queueIndex !== -1 && ( <> )} {canPauseDownload(download.status) && ( )} {canStartDownload(download.status) && ( )}
); });