import { NormalizedTorrent, TorrentState } from '@ctrl/shared-torrent'; import { Badge, Center, Flex, Group, Loader, ScrollArea, Stack, Table, Text, Title, } from '@mantine/core'; import { useElementSize } from '@mantine/hooks'; import { IconFileDownload, IconInfoCircle } from '@tabler/icons-react'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import relativeTime from 'dayjs/plugin/relativeTime'; import { useTranslation } from 'next-i18next'; import { useCardStyles } from '~/components/layout/Common/useCardStyles'; import { MIN_WIDTH_MOBILE } from '~/constants/constants'; import { NormalizedDownloadQueueResponse } from '~/types/api/downloads/queue/NormalizedDownloadQueueResponse'; import { AppIntegrationType } from '~/types/app'; import { useGetDownloadClientsQueue } from '../download-speed/useGetNetworkSpeed'; import { defineWidget } from '../helper'; import { IWidget } from '../widgets'; import { BitTorrentQueueItem } from './TorrentQueueItem'; dayjs.extend(duration); dayjs.extend(relativeTime); const downloadAppTypes: AppIntegrationType['type'][] = ['deluge', 'qBittorrent', 'transmission']; const definition = defineWidget({ id: 'torrents-status', icon: IconFileDownload, options: { displayCompletedTorrents: { type: 'switch', defaultValue: true, }, displayStaleTorrents: { type: 'switch', defaultValue: true, }, labelFilterIsWhitelist: { type: 'switch', defaultValue: true, }, labelFilter: { type: 'multiple-text', defaultValue: [] as string[], }, }, gridstack: { minWidth: 2, minHeight: 2, maxWidth: 12, maxHeight: 14, }, component: TorrentTile, }); export type ITorrent = IWidget<(typeof definition)['id'], typeof definition>; interface TorrentTileProps { widget: ITorrent; } function TorrentTile({ widget }: TorrentTileProps) { const { t } = useTranslation('modules/torrents-status'); const { width, ref } = useElementSize(); const { classes } = useCardStyles(true); const { data, isError, isInitialLoading, dataUpdatedAt, }: { data: NormalizedDownloadQueueResponse | undefined; isError: boolean; isInitialLoading: boolean; dataUpdatedAt: number; } = useGetDownloadClientsQueue(); if (isError) { return ( {t('card.errors.generic.title')} {t('card.errors.generic.text')} ); } if (isInitialLoading || !data) { return ( {t('card.loading.title')} {t('card.loading.description')} ); } if (data.apps.length === 0) { return ( {t('card.errors.noDownloadClients.title')} {t('card.errors.noDownloadClients.text')} ); } if (!data || Object.values(data.apps).length < 1) { return (
{t('card.table.body.nothingFound')}
); } const torrents = data.apps.flatMap((app) => (app.type === 'torrent' ? app.torrents : [])); const filteredTorrents = filterTorrents(widget, torrents); const difference = new Date().getTime() - dataUpdatedAt; const duration = dayjs.duration(difference, 'ms'); const humanizedDuration = duration.humanize(); return ( {width > MIN_WIDTH_MOBILE && } {width > MIN_WIDTH_MOBILE && } {width > MIN_WIDTH_MOBILE && } {filteredTorrents.map((torrent, index) => ( ))} {filteredTorrents.length !== torrents.length && ( )}
{t('card.table.header.name')} {t('card.table.header.size')}{t('card.table.header.download')}{t('card.table.header.upload')}{t('card.table.header.estimatedTimeOfArrival')}{t('card.table.header.progress')}
MIN_WIDTH_MOBILE ? 6 : 3}> {t('card.table.body.filterHidingItems', { count: torrents.length - filteredTorrents.length, })}
{data.apps.some((x) => !x.success) && ( {t('card.footer.error')} )} {t('card.footer.lastUpdated', { time: humanizedDuration })}
); } export const filterTorrents = (widget: ITorrent, torrents: NormalizedTorrent[]) => { let result = torrents; if (!widget.properties.displayCompletedTorrents) { result = result.filter((torrent) => !torrent.isCompleted); } if (widget.properties.labelFilter.length > 0) { result = filterTorrentsByLabels( result, widget.properties.labelFilter, widget.properties.labelFilterIsWhitelist ); } result = filterStaleTorrent(widget, result); return result; }; const filterStaleTorrent = (widget: ITorrent, torrents: NormalizedTorrent[]) => { if (widget.properties.displayStaleTorrents) { return torrents; } return torrents.filter((torrent) => torrent.isCompleted || torrent.downloadSpeed > 0); }; const filterTorrentsByLabels = ( torrents: NormalizedTorrent[], labels: string[], isWhitelist: boolean ) => { if (isWhitelist) { return torrents.filter((torrent) => torrent.label && labels.includes(torrent.label)); } return torrents.filter((torrent) => !labels.includes(torrent.label as string)); }; export default definition;