import { ReactElement, useLayoutEffect, useRef } from 'react' import CloseIcon from 'remixicon-react/CloseLineIcon' import ArrowDownIcon from 'remixicon-react/ArrowDownSLineIcon' import './FileProgressWindow.scss' import { GetIconElement } from '../../utils/GetIconElement' import { ProgressBar } from '../ProgressBar/ProgressBar' import { FileTransferType, TransferBarColor, TransferStatus, ProgressItem } from '../../constants/transfers' import { capitalizeFirstLetter, truncateNameMiddle } from '../../utils/common' import { guessMime } from '../../utils/view' interface FileProgressWindowProps { items?: ProgressItem[] type: FileTransferType onCancelClick: () => void onRowClose?: (uuid: string) => void onCloseAll?: () => void } const formatEta = (sec?: number) => { if (sec === undefined || sec === null) return '' if (sec <= 0) return 'Done' const s = Math.ceil(sec) const mm = Math.floor(s / 60) const ss = s % 60 return mm > 0 ? `${mm}m ${ss}s left` : `${ss}s left` } const formatDuration = (sec?: number) => { if (sec === undefined || sec === null) return '' const s = Math.max(0, Math.round(sec)) const mm = Math.floor(s / 60) const ss = s % 60 return mm > 0 ? `${mm}m ${ss}s` : `${ss}s` } export function FileProgressWindow({ items, type, onCancelClick, onRowClose, onCloseAll, }: FileProgressWindowProps): ReactElement | null { const listRef = useRef(null) const firstRowRef = useRef(null) const count = items?.length ?? 0 const rows: ProgressItem[] = items ?? [] const getTransferInfo = (item: ProgressItem, pct?: number) => { const transferType = capitalizeFirstLetter(item?.kind ?? type) const verb = `${transferType}ing` const actualStatus = item.status || (pct && pct >= 100 ? TransferStatus.Done : verb) return { statusText: capitalizeFirstLetter(actualStatus), barColor: TransferBarColor[transferType as keyof typeof TransferBarColor], } } const allDone = rows.length > 0 && rows.every(r => { const pct = Number.isFinite(r.percent) ? Math.round(r.percent as number) : undefined return ( r.status === TransferStatus.Done || r.status === TransferStatus.Error || r.status === TransferStatus.Cancelled || (typeof pct === 'number' && pct >= 100) ) }) useLayoutEffect(() => { const rowEl = firstRowRef.current const listEl = listRef.current if (!rowEl || !listEl) return const rowH = rowEl.getBoundingClientRect().height const safeRowH = rowH > 0 ? rowH : 72 listEl.style.maxHeight = `${safeRowH * 5}px` }, [rows.length]) return (
{count} {type} {count === 1 ? '' : 's'}
{rows.map((item, idx) => { const pctNum = Number.isFinite(item.percent) ? Math.max(0, Math.min(100, Math.round(item.percent as number))) : undefined const isComplete = (pctNum ?? 0) >= 100 || item.status === TransferStatus.Done const isActive = item.status === TransferStatus.Uploading || item.status === TransferStatus.Downloading || item.status === TransferStatus.Queued const rowActionLabel = isActive ? 'Cancel' : 'Dismiss' const transferInfo = getTransferInfo(item, pctNum) const getCenterText = () => { if (!isComplete && typeof item.etaSec === 'number') return formatEta(item.etaSec) if (isComplete && typeof item.elapsedSec === 'number') return formatDuration(item.elapsedSec) return '' } const centerDisplay = getCenterText() || '\u00A0' const { mime } = guessMime(item.name) const mimeType = mime.split('/')[0].toLowerCase() || 'file' return (
{truncateNameMiddle(item.name, 25, 8, 8)}
{item.driveName && (
{truncateNameMiddle(item.driveName, 25, 8, 8)}
)}
{typeof pctNum === 'number' ? `${pctNum}%` : ''}
{item.size || '—'}
{centerDisplay}
{transferInfo.statusText}
) })}
) }