import { NormalizedTorrent } from '@ctrl/shared-torrent'; import { Center, Flex, Group, Loader, ScrollArea, Stack, Table, Text, Title, useMantineTheme, } from '@mantine/core'; import { useElementSize } from '@mantine/hooks'; import { IconFileDownload } from '@tabler/icons'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import relativeTime from 'dayjs/plugin/relativeTime'; import { useTranslation } from 'next-i18next'; import { useEffect, useState } from 'react'; import { useConfigContext } from '../../config/provider'; import { useGetTorrentData } from '../../hooks/widgets/torrents/useGetTorrentData'; import { AppIntegrationType } from '../../types/app'; import { defineWidget } from '../helper'; import { IWidget } from '../widgets'; import { BitTorrrentQueueItem } from './BitTorrentQueueItem'; 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, }, }, gridstack: { minWidth: 4, minHeight: 5, maxWidth: 12, maxHeight: 14, }, component: BitTorrentTile, }); export type IBitTorrent = IWidget; interface BitTorrentTileProps { widget: IBitTorrent; } function BitTorrentTile({ widget }: BitTorrentTileProps) { const { t } = useTranslation('modules/torrents-status'); const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs; const { width } = useElementSize(); const { config } = useConfigContext(); const downloadApps = config?.apps.filter((x) => x.integration && downloadAppTypes.includes(x.integration.type)) ?? []; const [selectedAppId, setSelectedApp] = useState(downloadApps[0]?.id); const { data, isError, isInitialLoading, dataUpdatedAt } = useGetTorrentData({ appId: selectedAppId!, }); useEffect(() => { if (!selectedAppId && downloadApps.length) { setSelectedApp(downloadApps[0].id); } }, [downloadApps, selectedAppId]); if (downloadApps.length === 0) { return ( {t('card.errors.noDownloadClients.title')} {t('card.errors.noDownloadClients.text')} ); } if (isError) { return ( {t('card.errors.generic.title')} {t('card.errors.generic.text')} ); } if (isInitialLoading) { return ( {t('card.loading.title')} Homarr is establishing a connection... ); } if (!data || data.length < 1) { return (
{t('card.table.body.nothingFound')}
); } const filter = (torrent: NormalizedTorrent) => { if (!widget.properties.displayCompletedTorrents && torrent.isCompleted) { return false; } if (!widget.properties.displayStaleTorrents && !torrent.isCompleted && torrent.eta <= 0) { return false; } return true; }; 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 && } {data.filter(filter).map((item: NormalizedTorrent, index: number) => ( ))}
{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')}
Last updated {humanizedDuration} ago
); } export default definition;