1 import { useEffect, useMemo, useRef, useState } from 'react';
3 import { c, msgid } from 'ttag';
5 import { Button } from '@proton/atoms';
6 import { Icon, Tooltip } from '@proton/components';
7 import clsx from '@proton/utils/clsx';
15 isTransferManuallyPaused,
17 } from '../../utils/transfer';
18 import type { Download, TransfersStats, Upload } from './transfer';
21 downloads: Download[];
23 stats: TransfersStats;
25 onToggleMinimize: () => void;
29 const Header = ({ downloads, uploads, stats, onClose, onToggleMinimize, minimized = false }: Props) => {
30 const [uploadsInSession, setUploadsInSession] = useState<Upload[]>([]);
31 const [downloadsInSession, setDownloadsInSession] = useState<Download[]>([]);
33 const minimizeRef = useRef<HTMLButtonElement>(null);
34 const transfers = useMemo(() => [...downloads, ...uploads], [uploads, downloads]);
36 const activeUploads = useMemo(() => uploads.filter(isTransferActive), [uploads]);
37 const activeDownloads = useMemo(() => downloads.filter(isTransferActive), [downloads]);
39 const doneUploads = useMemo(() => uploads.filter(isTransferDone), [uploads]);
40 const doneDownloads = useMemo(() => downloads.filter(isTransferDone), [downloads]);
42 const pausedTransfers = useMemo(() => transfers.filter(isTransferManuallyPaused), [transfers]);
43 const failedTransfers = useMemo(() => transfers.filter(isTransferError), [transfers]);
44 const canceledTransfers = useMemo(() => transfers.filter(isTransferCanceled), [transfers]);
45 const skippedTransfers = useMemo(() => transfers.filter(isTransferSkipped), [transfers]);
47 const activeUploadsCount = activeUploads.length;
48 const activeDownloadsCount = activeDownloads.length;
51 if (activeUploadsCount) {
52 setUploadsInSession((uploadsInSession) => [
53 ...doneUploads.filter((done) => uploadsInSession.some(({ id }) => id === done.id)),
57 setUploadsInSession([]);
59 }, [activeUploads, doneUploads, activeUploadsCount]);
62 if (activeDownloadsCount) {
63 setDownloadsInSession((downloadsInSession) => [
64 ...doneDownloads.filter((done) => downloadsInSession.some(({ id }) => id === done.id)),
68 setDownloadsInSession([]);
70 }, [activeDownloads, activeDownloadsCount]);
72 const getHeadingText = () => {
73 const headingElements: string[] = [];
75 const activeCount = activeUploadsCount + activeDownloadsCount;
76 const doneUploadsCount = doneUploads.length;
77 const doneDownloadsCount = doneDownloads.length;
78 const doneCount = doneUploadsCount + doneDownloadsCount;
80 const errorCount = failedTransfers.length;
81 const canceledCount = canceledTransfers.length;
82 const skippedCount = skippedTransfers.length;
83 const pausedCount = pausedTransfers.length;
86 if (doneUploadsCount && doneDownloadsCount) {
88 c('Info').ngettext(msgid`${doneCount} finished`, `${doneCount} finished`, doneCount)
91 if (doneUploadsCount) {
94 msgid`${doneUploadsCount} uploaded`,
95 `${doneUploadsCount} uploaded`,
100 if (doneDownloadsCount) {
101 headingElements.push(
103 msgid`${doneDownloadsCount} downloaded`,
104 `${doneDownloadsCount} downloaded`,
112 if (activeUploadsCount) {
113 const uploadProgress = calculateProgress(stats, uploadsInSession);
114 headingElements.push(
116 msgid`${activeUploadsCount} uploading (${uploadProgress}%)`,
117 `${activeUploadsCount} uploading (${uploadProgress}%)`,
122 if (activeDownloadsCount) {
123 if (downloadsInSession.some(({ meta: { size } }) => size === undefined)) {
124 headingElements.push(
126 msgid`${activeDownloadsCount} downloading`,
127 `${activeDownloadsCount} downloading`,
132 const downloadProgress = calculateProgress(stats, downloadsInSession);
133 headingElements.push(
135 msgid`${activeDownloadsCount} downloading (${downloadProgress}%)`,
136 `${activeDownloadsCount} downloading (${downloadProgress}%)`,
144 headingElements.push(
145 c('Info').ngettext(msgid`${pausedCount} paused`, `${pausedCount} paused`, pausedCount)
150 headingElements.push(
151 c('Info').ngettext(msgid`${canceledCount} canceled`, `${canceledCount} canceled`, canceledCount)
156 headingElements.push(
157 // translator: Shown in the transfer manager header - ex. "3 skipped"
158 c('Info').ngettext(msgid`${skippedCount} skipped`, `${skippedCount} skipped`, skippedCount)
163 headingElements.push(c('Info').ngettext(msgid`${errorCount} failed`, `${errorCount} failed`, errorCount));
166 return headingElements.join(', ');
169 const minMaxTitle = minimized ? c('Action').t`Maximize transfers` : c('Action').t`Minimize transfers`;
170 const closeTitle = c('Action').t`Close transfers`;
173 <div className="transfers-manager-heading ui-prominent flex items-center flex-nowrap px-2">
176 className="flex-1 p-2"
179 data-testid="drive-transfers-manager:header"
180 onClick={minimized ? onToggleMinimize : undefined}
184 <Tooltip title={minMaxTitle}>
192 minimizeRef.current?.blur();
194 aria-expanded={!minimized}
195 aria-controls="transfer-manager"
197 <Icon className={clsx(['m-auto', minimized && 'rotateX-180'])} name="low-dash" />
198 <span className="sr-only">{minMaxTitle}</span>
201 <Tooltip title={closeTitle}>
202 <Button icon type="button" shape="ghost" data-testid="drive-transfers-manager:close" onClick={onClose}>
203 <Icon className="m-auto" name="cross" alt={closeTitle} />
210 export default Header;