import React, { useState, useEffect } from 'react'; import { useDownloadStore, DownloadItem } from '../store/useDownloadStore'; import { useSettingsStore } from '../store/useSettingsStore'; import { SidebarFilter } from './Sidebar'; import { Play, Pause, Plus, FileText, Image as ImageIcon, Music, Film, Box, Archive, FileQuestion, PanelLeft, ArrowDownCircle, Command } from 'lucide-react'; import { DownloadItem as DownloadItemComponent } from './DownloadItem'; import { invokeCommand as invoke } from '../ipc'; import { homeDir } from '@tauri-apps/api/path'; interface DownloadTableProps { filter: SidebarFilter; } export const DownloadTable: React.FC = ({ filter }) => { const { downloads, toggleAddModal, openDeleteModal, redownload } = useDownloadStore(); const { isSidebarVisible, toggleSidebar } = useSettingsStore(); const isMac = navigator.userAgent.includes('Mac'); const [contextMenu, setContextMenu] = useState<{ x: number; y: number; id: string } | null>(null); const [interactionError, setInteractionError] = useState(''); const [columnWidths, setColumnWidths] = useState([340, 100, 220, 100, 80, 170]); const columnMinimums = [0, 58, 92, 58, 48, 112]; const tableGridTemplate = columnWidths.map((width, index) => `minmax(${columnMinimums[index]}px, ${width}fr)`).join(' '); const startColumnResize = (index: number, event: React.PointerEvent) => { event.preventDefault(); event.stopPropagation(); const startX = event.clientX; const startWidth = columnWidths[index]; const handlePointerMove = (moveEvent: PointerEvent) => { const nextWidth = Math.max(columnMinimums[index], startWidth + moveEvent.clientX - startX); setColumnWidths(widths => widths.map((width, columnIndex) => columnIndex === index ? nextWidth : width)); }; const handlePointerUp = () => { window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointerup', handlePointerUp); document.body.classList.remove('is-resizing'); }; document.body.classList.add('is-resizing'); window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointerup', handlePointerUp); }; useEffect(() => { const handleCloseMenu = () => setContextMenu(null); window.addEventListener('click', handleCloseMenu); return () => window.removeEventListener('click', handleCloseMenu); }, []); useEffect(() => { if (!interactionError) return; const timeout = window.setTimeout(() => setInteractionError(''), 5000); return () => window.clearTimeout(timeout); }, [interactionError]); const resolvePath = async (dir: string, file: string) => { let resolvedDir = dir; if (dir.startsWith('~/')) { const home = await homeDir(); resolvedDir = home + '/' + dir.slice(2); } else if (dir === '~') { resolvedDir = await homeDir(); } const separator = resolvedDir.endsWith('/') ? '' : '/'; return resolvedDir + separator + file; }; const showInteractionError = (message: string, error: unknown) => { const detail = typeof error === 'string' ? error : error instanceof Error ? error.message : String(error); setInteractionError(`${message}: ${detail}`); }; const getDownloadPath = async (item: DownloadItem) => { const fileName = item.fileName?.trim(); if (!fileName) return null; const settings = useSettingsStore.getState(); const destination = item.destination || (settings.downloadDirectories && settings.downloadDirectories[item.category]) || settings.defaultDownloadPath || '~/Downloads'; return resolvePath(destination, fileName); }; const openProperties = (id: string) => { useDownloadStore.getState().setSelectedPropertiesDownloadId(id); }; const openDownloadFile = async (item: DownloadItem) => { if (item.status !== 'completed') { openProperties(item.id); return; } const fullPath = await getDownloadPath(item); if (!fullPath) { openProperties(item.id); return; } try { await invoke('open_downloaded_file', { path: fullPath }); } catch (error) { console.error("Failed to open file:", error); showInteractionError('Could not open downloaded file', error); } }; const revealDownloadFile = async (item: DownloadItem) => { if (item.status !== 'completed') { openProperties(item.id); return; } const fullPath = await getDownloadPath(item); if (!fullPath) { openProperties(item.id); return; } try { await invoke('reveal_in_file_manager', { path: fullPath }); } catch (error) { console.error("Failed to show in Finder:", error); showInteractionError('Could not show download in Finder', error); } }; const handleDownloadDoubleClick = (item: DownloadItem) => { if (item.status === 'completed') { void openDownloadFile(item); return; } openProperties(item.id); }; const filteredDownloads = downloads.filter((d: DownloadItem) => { if (filter.startsWith('queue:')) { return d.queueId === filter.replace('queue:', ''); } switch (filter) { case 'all': return true; case 'active': return d.status === 'downloading'; case 'completed': return d.status === 'completed'; case 'unfinished': return d.status !== 'completed'; default: return d.category === filter; } }); const getFilterTitle = () => { if (filter.startsWith('queue:')) { const qid = filter.replace('queue:', ''); const queue = useDownloadStore.getState().queues.find(q => q.id === qid); return queue ? queue.name : 'Unknown Queue'; } switch (filter) { case 'all': return 'All Downloads'; case 'active': return 'Active'; case 'completed': return 'Completed'; case 'unfinished': return 'Unfinished'; default: return filter; } }; const handlePause = async (id: string) => { try { await invoke('pause_download', { id }); } catch (e) { console.error("Failed to pause:", e); } }; const handleResume = (item: DownloadItem) => { useDownloadStore.getState().resumeDownload(item.id); }; const handleDelete = (id: string) => { openDeleteModal(id); }; const contextItem = contextMenu ? downloads.find(d => d.id === contextMenu.id) : null; const getCategoryIcon = (category: string) => { switch(category) { case 'Musics': return ; case 'Movies': return ; case 'Documents': return ; case 'Applications': return ; case 'Pictures': return ; case 'Compressed': return ; case 'Other': return ; default: return ; } } return (
{!isSidebarVisible && ( )}
Firelink
{getFilterTitle()} {filteredDownloads.length}
{filteredDownloads.length === 0 ? (
) : ( <>
{['File Name', 'Size', 'Status', 'Speed', 'ETA', 'Date Added'].map((label, index) => (
{label}
startColumnResize(index, event)} />
))}
{filteredDownloads.map((d, index) => ( ))} {Array.from({ length: Math.max(0, 50 - filteredDownloads.length) }).map((_, i) => { const globalIndex = filteredDownloads.length + i; return (
); })}
)}
{/* Floating Context Menu */} {contextMenu && contextItem && (
e.stopPropagation()} > {contextItem.status === 'completed' && ( )}
{(contextItem.status === 'downloading' || contextItem.status === 'queued' || contextItem.status === 'retrying') && ( )} {(contextItem.status === 'paused' || contextItem.status === 'failed' || contextItem.status === 'retrying') && ( )} {['completed', 'failed', 'paused'].includes(contextItem.status) && ( )}
{contextItem.status === 'completed' && ( )}
)} {interactionError && (
{interactionError}
)}
); };