Files
Prowlarr/frontend/src/Search/Mobile/SearchIndexOverview.tsx
T
Mark McDowall 16254cf5f9 New: Option to select download client when multiple of the same type are configured
(cherry picked from commit 07f0fbf9a51d54e44681fd0f74df4e048bff561a)
2024-05-08 03:14:17 +03:00

263 lines
7.3 KiB
TypeScript

import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import TextTruncate from 'react-text-truncate';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import DownloadProtocol from 'DownloadClient/DownloadProtocol';
import { icons, kinds } from 'Helpers/Props';
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
import { IndexerCategory } from 'Indexer/Indexer';
import OverrideMatchModal from 'Search/OverrideMatch/OverrideMatchModal';
import CategoryLabel from 'Search/Table/CategoryLabel';
import Peers from 'Search/Table/Peers';
import createEnabledDownloadClientsSelector from 'Store/Selectors/createEnabledDownloadClientsSelector';
import dimensions from 'Styles/Variables/dimensions';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge';
import formatBytes from 'Utilities/Number/formatBytes';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import styles from './SearchIndexOverview.css';
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
const columnPaddingSmallScreen = parseInt(
dimensions.movieIndexColumnPaddingSmallScreen
);
function getDownloadIcon(
isGrabbing: boolean,
isGrabbed: boolean,
grabError?: string
) {
if (isGrabbing) {
return icons.SPINNER;
} else if (isGrabbed) {
return icons.DOWNLOADING;
} else if (grabError) {
return icons.DOWNLOADING;
}
return icons.DOWNLOAD;
}
function getDownloadKind(isGrabbed: boolean, grabError?: string) {
if (isGrabbed) {
return kinds.SUCCESS;
}
if (grabError) {
return kinds.DANGER;
}
return kinds.DEFAULT;
}
function getDownloadTooltip(
isGrabbing: boolean,
isGrabbed: boolean,
grabError?: string
) {
if (isGrabbing) {
return '';
} else if (isGrabbed) {
return translate('AddedToDownloadClient');
} else if (grabError) {
return grabError;
}
return translate('AddToDownloadClient');
}
interface SearchIndexOverviewProps {
guid: string;
protocol: DownloadProtocol;
age: number;
ageHours: number;
ageMinutes: number;
publishDate: string;
title: string;
infoUrl: string;
downloadUrl?: string;
magnetUrl?: string;
indexerId: number;
indexer: string;
categories: IndexerCategory[];
size: number;
seeders?: number;
leechers?: number;
indexerFlags: string[];
isGrabbing: boolean;
isGrabbed: boolean;
grabError?: string;
longDateFormat: string;
timeFormat: string;
rowHeight: number;
isSmallScreen: boolean;
onGrabPress(...args: unknown[]): void;
}
function SearchIndexOverview(props: SearchIndexOverviewProps) {
const {
guid,
indexerId,
protocol,
categories,
age,
ageHours,
ageMinutes,
publishDate,
title,
infoUrl,
downloadUrl,
magnetUrl,
indexer,
size,
seeders,
leechers,
indexerFlags = [],
isGrabbing = false,
isGrabbed = false,
grabError,
longDateFormat,
timeFormat,
rowHeight,
isSmallScreen,
onGrabPress,
} = props;
const [isOverrideModalOpen, setIsOverrideModalOpen] = useState(false);
const { items: downloadClients } = useSelector(
createEnabledDownloadClientsSelector(protocol)
);
const onGrabPressWrapper = useCallback(() => {
onGrabPress({
guid,
indexerId,
});
}, [guid, indexerId, onGrabPress]);
const onOverridePress = useCallback(() => {
setIsOverrideModalOpen(true);
}, [setIsOverrideModalOpen]);
const onOverrideModalClose = useCallback(() => {
setIsOverrideModalOpen(false);
}, [setIsOverrideModalOpen]);
const contentHeight = useMemo(() => {
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
return rowHeight - padding;
}, [rowHeight, isSmallScreen]);
return (
<>
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.info} style={{ height: contentHeight }}>
<div className={styles.titleRow}>
<div className={styles.title}>
<Link to={infoUrl} title={title}>
<TextTruncate line={2} text={title} />
</Link>
</div>
<div className={styles.actions}>
<SpinnerIconButton
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
kind={getDownloadKind(isGrabbed, grabError)}
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
isDisabled={isGrabbed}
isSpinning={isGrabbing}
onPress={onGrabPressWrapper}
/>
{downloadClients.length > 1 ? (
<Link
className={styles.manualDownloadContent}
title={translate('OverrideAndAddToDownloadClient')}
onPress={onOverridePress}
>
<div className={styles.manualDownloadContent}>
<Icon
className={styles.interactiveIcon}
name={icons.INTERACTIVE}
size={11}
/>
<Icon
className={styles.downloadIcon}
name={icons.CIRCLE_DOWN}
size={9}
/>
</div>
</Link>
) : null}
{downloadUrl || magnetUrl ? (
<IconButton
className={styles.downloadLink}
name={icons.SAVE}
title={translate('Save')}
to={downloadUrl ?? magnetUrl}
/>
) : null}
</div>
</div>
<div className={styles.indexerRow}>{indexer}</div>
<div className={styles.infoRow}>
<ProtocolLabel protocol={protocol} />
{protocol === 'torrent' && (
<Peers seeders={seeders} leechers={leechers} />
)}
<Label>{formatBytes(size)}</Label>
<Label
title={formatDateTime(publishDate, longDateFormat, timeFormat, {
includeSeconds: true,
})}
>
{formatAge(age, ageHours, ageMinutes)}
</Label>
<CategoryLabel categories={categories} />
{indexerFlags.length
? indexerFlags
.sort((a, b) => a.localeCompare(b))
.map((flag, index) => {
return (
<Label key={index} kind={kinds.INFO}>
{titleCase(flag)}
</Label>
);
})
: null}
</div>
</div>
</div>
</div>
<OverrideMatchModal
isOpen={isOverrideModalOpen}
title={title}
indexerId={indexerId}
guid={guid}
protocol={protocol}
isGrabbing={isGrabbing}
grabError={grabError}
onModalClose={onOverrideModalClose}
/>
</>
);
}
export default SearchIndexOverview;