2023-03-27 23:49:12 +00:00
|
|
|
import React, { useCallback, useState } from 'react';
|
|
|
|
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
|
|
|
import Icon from 'Components/Icon';
|
|
|
|
import Link from 'Components/Link/Link';
|
|
|
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
|
|
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|
|
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|
|
|
import TableRow from 'Components/Table/TableRow';
|
|
|
|
import Popover from 'Components/Tooltip/Popover';
|
|
|
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
|
|
|
import type DownloadProtocol from 'DownloadClient/DownloadProtocol';
|
|
|
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
|
|
|
import Language from 'Language/Language';
|
|
|
|
import MovieFormats from 'Movie/MovieFormats';
|
|
|
|
import MovieLanguage from 'Movie/MovieLanguage';
|
|
|
|
import MovieQuality from 'Movie/MovieQuality';
|
|
|
|
import { QualityModel } from 'Quality/Quality';
|
|
|
|
import CustomFormat from 'typings/CustomFormat';
|
|
|
|
import MovieBlocklist from 'typings/MovieBlocklist';
|
|
|
|
import MovieHistory from 'typings/MovieHistory';
|
|
|
|
import formatDateTime from 'Utilities/Date/formatDateTime';
|
|
|
|
import formatAge from 'Utilities/Number/formatAge';
|
|
|
|
import formatBytes from 'Utilities/Number/formatBytes';
|
|
|
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
|
|
|
import translate from 'Utilities/String/translate';
|
|
|
|
import OverrideMatchModal from './OverrideMatch/OverrideMatchModal';
|
|
|
|
import Peers from './Peers';
|
|
|
|
import styles from './InteractiveSearchRow.css';
|
|
|
|
|
|
|
|
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) {
|
2023-08-28 00:35:47 +00:00
|
|
|
return translate('AddedToDownloadQueue');
|
2023-03-27 23:49:12 +00:00
|
|
|
} else if (grabError) {
|
|
|
|
return grabError;
|
|
|
|
}
|
|
|
|
|
2023-08-28 00:35:47 +00:00
|
|
|
return translate('AddToDownloadQueue');
|
2023-03-27 23:49:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
interface InteractiveSearchRowProps {
|
|
|
|
guid: string;
|
|
|
|
protocol: DownloadProtocol;
|
|
|
|
age: number;
|
|
|
|
ageHours: number;
|
|
|
|
ageMinutes: number;
|
|
|
|
publishDate: string;
|
|
|
|
title: string;
|
|
|
|
infoUrl: string;
|
|
|
|
indexerId: number;
|
|
|
|
indexer: string;
|
|
|
|
size: number;
|
|
|
|
seeders?: number;
|
|
|
|
leechers?: number;
|
|
|
|
quality: QualityModel;
|
|
|
|
languages: Language[];
|
|
|
|
customFormats: CustomFormat[];
|
|
|
|
customFormatScore: number;
|
|
|
|
mappedMovieId?: number;
|
2024-02-08 10:10:41 +00:00
|
|
|
indexerFlags: string[];
|
2023-03-27 23:49:12 +00:00
|
|
|
rejections: string[];
|
|
|
|
downloadAllowed: boolean;
|
|
|
|
isGrabbing: boolean;
|
|
|
|
isGrabbed: boolean;
|
|
|
|
grabError?: string;
|
|
|
|
historyFailedData?: MovieHistory;
|
|
|
|
historyGrabbedData?: MovieHistory;
|
|
|
|
blocklistData?: MovieBlocklist;
|
|
|
|
longDateFormat: string;
|
|
|
|
timeFormat: string;
|
|
|
|
searchPayload: object;
|
|
|
|
onGrabPress(...args: unknown[]): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|
|
|
const {
|
|
|
|
guid,
|
|
|
|
indexerId,
|
|
|
|
protocol,
|
|
|
|
age,
|
|
|
|
ageHours,
|
|
|
|
ageMinutes,
|
|
|
|
publishDate,
|
|
|
|
title,
|
|
|
|
infoUrl,
|
|
|
|
indexer,
|
|
|
|
size,
|
|
|
|
seeders,
|
|
|
|
leechers,
|
|
|
|
quality,
|
|
|
|
languages,
|
|
|
|
customFormatScore,
|
|
|
|
customFormats,
|
|
|
|
mappedMovieId,
|
2024-02-08 10:10:41 +00:00
|
|
|
indexerFlags = [],
|
2023-03-27 23:49:12 +00:00
|
|
|
rejections = [],
|
|
|
|
downloadAllowed,
|
|
|
|
isGrabbing = false,
|
|
|
|
isGrabbed = false,
|
|
|
|
longDateFormat,
|
|
|
|
timeFormat,
|
|
|
|
grabError,
|
2023-11-21 01:45:19 +00:00
|
|
|
historyGrabbedData = {} as MovieHistory,
|
|
|
|
historyFailedData = {} as MovieHistory,
|
|
|
|
blocklistData = {} as MovieBlocklist,
|
2023-03-27 23:49:12 +00:00
|
|
|
searchPayload,
|
|
|
|
onGrabPress,
|
|
|
|
} = props;
|
|
|
|
|
|
|
|
const [isConfirmGrabModalOpen, setIsConfirmGrabModalOpen] = useState(false);
|
|
|
|
const [isOverrideModalOpen, setIsOverrideModalOpen] = useState(false);
|
|
|
|
|
|
|
|
const onGrabPressWrapper = useCallback(() => {
|
|
|
|
if (downloadAllowed) {
|
|
|
|
onGrabPress({
|
|
|
|
guid,
|
|
|
|
indexerId,
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
setIsConfirmGrabModalOpen(true);
|
|
|
|
}, [
|
|
|
|
guid,
|
|
|
|
indexerId,
|
|
|
|
downloadAllowed,
|
|
|
|
onGrabPress,
|
|
|
|
setIsConfirmGrabModalOpen,
|
|
|
|
]);
|
|
|
|
|
|
|
|
const onGrabConfirm = useCallback(() => {
|
|
|
|
setIsConfirmGrabModalOpen(false);
|
|
|
|
|
|
|
|
onGrabPress({
|
|
|
|
guid,
|
|
|
|
indexerId,
|
|
|
|
...searchPayload,
|
|
|
|
});
|
|
|
|
}, [guid, indexerId, searchPayload, onGrabPress, setIsConfirmGrabModalOpen]);
|
|
|
|
|
|
|
|
const onGrabCancel = useCallback(() => {
|
|
|
|
setIsConfirmGrabModalOpen(false);
|
|
|
|
}, [setIsConfirmGrabModalOpen]);
|
|
|
|
|
|
|
|
const onOverridePress = useCallback(() => {
|
|
|
|
setIsOverrideModalOpen(true);
|
|
|
|
}, [setIsOverrideModalOpen]);
|
|
|
|
|
|
|
|
const onOverrideModalClose = useCallback(() => {
|
|
|
|
setIsOverrideModalOpen(false);
|
|
|
|
}, [setIsOverrideModalOpen]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<TableRow>
|
|
|
|
<TableRowCell className={styles.protocol}>
|
|
|
|
<ProtocolLabel protocol={protocol} />
|
|
|
|
</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell
|
|
|
|
className={styles.age}
|
|
|
|
title={formatDateTime(publishDate, longDateFormat, timeFormat, {
|
|
|
|
includeSeconds: true,
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
{formatAge(age, ageHours, ageMinutes)}
|
|
|
|
</TableRowCell>
|
|
|
|
|
2023-03-06 02:58:57 +00:00
|
|
|
<TableRowCell>
|
|
|
|
<div className={styles.titleContent}>
|
|
|
|
<Link to={infoUrl} title={title}>
|
|
|
|
{title}
|
|
|
|
</Link>
|
|
|
|
</div>
|
2023-03-27 23:49:12 +00:00
|
|
|
</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.indexer}>{indexer}</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.history}>
|
|
|
|
{historyGrabbedData?.date && !historyFailedData?.date ? (
|
|
|
|
<Icon
|
|
|
|
name={icons.DOWNLOADING}
|
|
|
|
kind={kinds.DEFAULT}
|
|
|
|
title={`${translate('Grabbed')}: ${formatDateTime(
|
|
|
|
historyGrabbedData.date,
|
|
|
|
longDateFormat,
|
|
|
|
timeFormat,
|
|
|
|
{ includeSeconds: true }
|
|
|
|
)}`}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
{historyFailedData?.date ? (
|
|
|
|
<Icon
|
|
|
|
name={icons.DOWNLOADING}
|
|
|
|
kind={kinds.DANGER}
|
|
|
|
title={`${translate('Failed')}: ${formatDateTime(
|
|
|
|
historyFailedData.date,
|
|
|
|
longDateFormat,
|
|
|
|
timeFormat,
|
|
|
|
{ includeSeconds: true }
|
|
|
|
)}`}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
{blocklistData?.date ? (
|
|
|
|
<Icon
|
|
|
|
className={
|
|
|
|
historyGrabbedData || historyFailedData ? styles.blocklist : ''
|
|
|
|
}
|
|
|
|
name={icons.BLOCKLIST}
|
|
|
|
kind={kinds.DANGER}
|
|
|
|
title={`${translate('Blocklisted')}: ${formatDateTime(
|
|
|
|
blocklistData.date,
|
|
|
|
longDateFormat,
|
|
|
|
timeFormat,
|
|
|
|
{ includeSeconds: true }
|
|
|
|
)}`}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.size}>{formatBytes(size)}</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.peers}>
|
|
|
|
{protocol === 'torrent' ? (
|
|
|
|
<Peers seeders={seeders} leechers={leechers} />
|
|
|
|
) : null}
|
|
|
|
</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.languages}>
|
|
|
|
<MovieLanguage languages={languages} />
|
|
|
|
</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.quality}>
|
2023-11-28 19:09:31 +00:00
|
|
|
<MovieQuality quality={quality} showRevision={true} />
|
2023-03-27 23:49:12 +00:00
|
|
|
</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.customFormatScore}>
|
|
|
|
<Tooltip
|
|
|
|
anchor={formatCustomFormatScore(
|
|
|
|
customFormatScore,
|
|
|
|
customFormats.length
|
|
|
|
)}
|
|
|
|
tooltip={<MovieFormats formats={customFormats} />}
|
2023-08-01 17:28:12 +00:00
|
|
|
position={tooltipPositions.LEFT}
|
2023-03-27 23:49:12 +00:00
|
|
|
/>
|
|
|
|
</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.indexerFlags}>
|
2024-02-08 10:10:41 +00:00
|
|
|
{indexerFlags.length ? (
|
2023-03-27 23:49:12 +00:00
|
|
|
<Popover
|
|
|
|
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
|
|
|
title={translate('IndexerFlags')}
|
2024-02-08 10:10:41 +00:00
|
|
|
body={
|
|
|
|
<ul>
|
|
|
|
{indexerFlags.map((flag, index) => {
|
|
|
|
return <li key={index}>{flag}</li>;
|
|
|
|
})}
|
|
|
|
</ul>
|
|
|
|
}
|
2023-03-27 23:49:12 +00:00
|
|
|
position={tooltipPositions.LEFT}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</TableRowCell>
|
|
|
|
|
2023-10-08 19:31:11 +00:00
|
|
|
<TableRowCell className={styles.rejected}>
|
|
|
|
{rejections.length ? (
|
|
|
|
<Popover
|
|
|
|
anchor={<Icon name={icons.DANGER} kind={kinds.DANGER} />}
|
|
|
|
title={translate('ReleaseRejected')}
|
|
|
|
body={
|
|
|
|
<ul>
|
|
|
|
{rejections.map((rejection, index) => {
|
|
|
|
return <li key={index}>{rejection}</li>;
|
|
|
|
})}
|
|
|
|
</ul>
|
|
|
|
}
|
|
|
|
position={tooltipPositions.LEFT}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</TableRowCell>
|
|
|
|
|
|
|
|
<TableRowCell className={styles.download}>
|
|
|
|
<SpinnerIconButton
|
|
|
|
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
|
|
|
kind={getDownloadKind(isGrabbed, grabError)}
|
|
|
|
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
|
|
|
isSpinning={isGrabbing}
|
|
|
|
onPress={onGrabPressWrapper}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<Link
|
|
|
|
className={styles.manualDownloadContent}
|
|
|
|
title={translate('OverrideAndAddToDownloadQueue')}
|
|
|
|
onPress={onOverridePress}
|
|
|
|
>
|
|
|
|
<div className={styles.manualDownloadContent}>
|
|
|
|
<Icon
|
|
|
|
className={styles.interactiveIcon}
|
|
|
|
name={icons.INTERACTIVE}
|
|
|
|
size={12}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<Icon
|
|
|
|
className={styles.downloadIcon}
|
|
|
|
name={icons.CIRCLE_DOWN}
|
|
|
|
size={10}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Link>
|
|
|
|
</TableRowCell>
|
|
|
|
|
2023-03-27 23:49:12 +00:00
|
|
|
<ConfirmModal
|
|
|
|
isOpen={isConfirmGrabModalOpen}
|
|
|
|
kind={kinds.WARNING}
|
|
|
|
title={translate('GrabRelease')}
|
|
|
|
message={translate('GrabReleaseMessageText', { title })}
|
|
|
|
confirmLabel={translate('Grab')}
|
|
|
|
onConfirm={onGrabConfirm}
|
|
|
|
onCancel={onGrabCancel}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<OverrideMatchModal
|
|
|
|
isOpen={isOverrideModalOpen}
|
|
|
|
title={title}
|
|
|
|
indexerId={indexerId}
|
|
|
|
guid={guid}
|
|
|
|
movieId={mappedMovieId}
|
|
|
|
languages={languages}
|
|
|
|
quality={quality}
|
|
|
|
protocol={protocol}
|
|
|
|
isGrabbing={isGrabbing}
|
|
|
|
grabError={grabError}
|
|
|
|
onModalClose={onOverrideModalClose}
|
|
|
|
/>
|
|
|
|
</TableRow>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default InteractiveSearchRow;
|