mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-25 22:37:27 -04:00
Override release grab modal
New: Option to override release and grab New: Option to select download client when multiple of the same type are configured (cherry picked from commit 07f0fbf9a51d54e44681fd0f74df4e048bff561a)
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
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) {
|
||||
return translate('AddToDownloadQueue');
|
||||
} else if (grabError) {
|
||||
return grabError;
|
||||
}
|
||||
|
||||
return translate('AddedToDownloadQueue');
|
||||
}
|
||||
|
||||
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;
|
||||
rejections: string[];
|
||||
indexerFlags: string[];
|
||||
episodeRequested: boolean;
|
||||
downloadAllowed: boolean;
|
||||
isDaily: 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,
|
||||
rejections = [],
|
||||
indexerFlags = [],
|
||||
downloadAllowed,
|
||||
isGrabbing = false,
|
||||
isGrabbed = false,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
grabError,
|
||||
historyGrabbedData,
|
||||
historyFailedData,
|
||||
blocklistData,
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
||||
<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.RIGHT}
|
||||
/>
|
||||
) : null}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.title}>
|
||||
<Link to={infoUrl} title={title}>
|
||||
<div>{title}</div>
|
||||
</Link>
|
||||
</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}>
|
||||
<MovieQuality quality={quality} />
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.customFormat}>
|
||||
<MovieFormats formats={customFormats} />
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.customFormatScore}>
|
||||
<Tooltip
|
||||
anchor={formatCustomFormatScore(
|
||||
customFormatScore,
|
||||
customFormats.length
|
||||
)}
|
||||
tooltip={<MovieFormats formats={customFormats} />}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.indexerFlags}>
|
||||
{indexerFlags.length ? (
|
||||
<Popover
|
||||
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||
title={translate('IndexerFlags')}
|
||||
body={
|
||||
<ul>
|
||||
{indexerFlags.map((flag, index) => {
|
||||
return <li key={index}>{flag}</li>;
|
||||
})}
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
) : null}
|
||||
</TableRowCell>
|
||||
|
||||
<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;
|
||||
Reference in New Issue
Block a user