mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-10 03:27:05 +00:00
147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import { useDownloadStore } from '../store/useDownloadStore';
|
|
import { useDownloadProgressStore } from '../store/downloadStore';
|
|
import { Play, Pause, MoreVertical } from 'lucide-react';
|
|
import type { DownloadItem as DownloadItemType } from '../bindings/DownloadItem';
|
|
|
|
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;
|
|
getCategoryIcon: (category: string) => React.ReactNode;
|
|
}
|
|
|
|
export const DownloadItem = React.memo<DownloadItemProps>(({
|
|
downloadId,
|
|
index,
|
|
tableGridTemplate,
|
|
setContextMenu,
|
|
handlePause,
|
|
handleResume,
|
|
getCategoryIcon,
|
|
}) => {
|
|
const download = useDownloadStore(state => state.downloads.find(d => d.id === downloadId));
|
|
|
|
const progressBarRef = useRef<HTMLDivElement>(null);
|
|
const statusTextRef = useRef<HTMLSpanElement>(null);
|
|
const speedTextRef = useRef<HTMLSpanElement>(null);
|
|
const etaTextRef = useRef<HTMLSpanElement>(null);
|
|
|
|
useEffect(() => {
|
|
// We only need transient updates while it's actively downloading
|
|
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)}%`;
|
|
}
|
|
if (speedTextRef.current) {
|
|
speedTextRef.current.innerText = progress.speed;
|
|
}
|
|
if (etaTextRef.current) {
|
|
etaTextRef.current.innerText = progress.eta;
|
|
}
|
|
});
|
|
|
|
return () => unsubscribe();
|
|
}, [downloadId, download?.status]);
|
|
|
|
if (!download) return null;
|
|
|
|
return (
|
|
<div
|
|
className={`download-row group cursor-default relative ${index % 2 !== 0 ? 'striped' : ''}`}
|
|
style={{ gridTemplateColumns: tableGridTemplate }}
|
|
onContextMenu={(e) => {
|
|
e.preventDefault();
|
|
setContextMenu({ x: e.clientX, y: e.clientY, id: download.id });
|
|
}}
|
|
>
|
|
<div className="download-file-cell">
|
|
<span className="shrink-0 text-text-muted">
|
|
{getCategoryIcon(download.category)}
|
|
</span>
|
|
<span className="download-file-name">
|
|
{download.fileName}
|
|
</span>
|
|
</div>
|
|
|
|
<div>
|
|
<span className="tabular-nums">
|
|
{download.size && download.size !== '-' ? download.size : 'Unknown'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="download-status-cell">
|
|
{download.status === 'completed' ? (
|
|
<span className="download-status download-status-completed">Completed</span>
|
|
) : (
|
|
<>
|
|
<div className="download-progress-track">
|
|
<div
|
|
ref={progressBarRef}
|
|
className={`download-progress-fill ${download.status === 'paused' ? 'paused' : ''}`}
|
|
style={{ width: `${(download.fraction || 0) * 100}%` }}
|
|
/>
|
|
</div>
|
|
<span
|
|
ref={statusTextRef}
|
|
className={`download-status ${download.status === 'paused' ? 'download-status-paused' : download.status === 'failed' ? 'download-status-failed' : download.status === 'downloading' ? 'download-status-downloading' : ''}`}
|
|
>
|
|
{download.status === 'downloading'
|
|
? `${((download.fraction || 0) * 100).toFixed(0)}%`
|
|
: download.status.charAt(0).toUpperCase() + download.status.slice(1)}
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<span ref={speedTextRef} className="tabular-nums">{download.status === 'downloading' ? download.speed : '-'}</span>
|
|
</div>
|
|
|
|
<div>
|
|
<span ref={etaTextRef} className="tabular-nums">{download.status === 'downloading' ? download.eta : '-'}</span>
|
|
</div>
|
|
|
|
<div className="download-cell-right">
|
|
<span className="truncate group-hover:hidden tabular-nums ml-auto">
|
|
{download.dateAdded ? new Date(download.dateAdded).toLocaleDateString() : '-'}
|
|
</span>
|
|
|
|
<div className="hidden group-hover:flex items-center justify-end gap-0.5 w-full ml-auto">
|
|
{download.status === 'downloading' && (
|
|
<button onClick={() => handlePause(download.id)} className="app-icon-button h-7 w-7" title="Pause">
|
|
<Pause size={14} fill="currentColor" />
|
|
</button>
|
|
)}
|
|
{download.status === 'paused' && (
|
|
<button onClick={() => handleResume(download)} className="app-icon-button h-7 w-7" title="Resume">
|
|
<Play size={14} fill="currentColor" />
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setContextMenu({ x: e.clientX, y: e.clientY, id: download.id });
|
|
}}
|
|
className="app-icon-button h-7 w-7"
|
|
title="Options"
|
|
>
|
|
<MoreVertical size={14} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|