mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-29 18:15:37 -04:00
New: Set Indexer flags in Manual Import
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import SelectIndexerFlagsModalContent from './SelectIndexerFlagsModalContent';
|
||||
|
||||
interface SelectIndexerFlagsModalProps {
|
||||
isOpen: boolean;
|
||||
indexerFlags: number;
|
||||
modalTitle: string;
|
||||
onIndexerFlagsSelect(indexerFlags: number): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
function SelectIndexerFlagsModal(props: SelectIndexerFlagsModalProps) {
|
||||
const {
|
||||
isOpen,
|
||||
indexerFlags,
|
||||
modalTitle,
|
||||
onIndexerFlagsSelect,
|
||||
onModalClose,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<SelectIndexerFlagsModalContent
|
||||
indexerFlags={indexerFlags}
|
||||
modalTitle={modalTitle}
|
||||
onIndexerFlagsSelect={onIndexerFlagsSelect}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectIndexerFlagsModal;
|
||||
@@ -0,0 +1,7 @@
|
||||
.modalBody {
|
||||
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
7
frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css.d.ts
vendored
Normal file
7
frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'modalBody': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
@@ -0,0 +1,75 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './SelectIndexerFlagsModalContent.css';
|
||||
|
||||
interface SelectIndexerFlagsModalContentProps {
|
||||
indexerFlags: number;
|
||||
modalTitle: string;
|
||||
onIndexerFlagsSelect(indexerFlags: number): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
function SelectIndexerFlagsModalContent(
|
||||
props: SelectIndexerFlagsModalContentProps
|
||||
) {
|
||||
const { modalTitle, onIndexerFlagsSelect, onModalClose } = props;
|
||||
const [indexerFlags, setIndexerFlags] = useState(props.indexerFlags);
|
||||
|
||||
const onIndexerFlagsChange = useCallback(
|
||||
({ value }: { value: number }) => {
|
||||
setIndexerFlags(value);
|
||||
},
|
||||
[setIndexerFlags]
|
||||
);
|
||||
|
||||
const onIndexerFlagsSelectWrapper = useCallback(() => {
|
||||
onIndexerFlagsSelect(indexerFlags);
|
||||
}, [indexerFlags, onIndexerFlagsSelect]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('SetIndexerFlagsModalTitle', { modalTitle })}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody
|
||||
className={styles.modalBody}
|
||||
scrollDirection={scrollDirections.NONE}
|
||||
>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('IndexerFlags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.INDEXER_FLAGS_SELECT}
|
||||
name="indexerFlags"
|
||||
indexerFlags={indexerFlags}
|
||||
autoFocus={true}
|
||||
onChange={onIndexerFlagsChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||
|
||||
<Button kind={kinds.SUCCESS} onPress={onIndexerFlagsSelectWrapper}>
|
||||
{translate('SetIndexerFlags')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectIndexerFlagsModalContent;
|
||||
@@ -26,6 +26,7 @@ import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
|
||||
import ImportMode from 'InteractiveImport/ImportMode';
|
||||
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
|
||||
import InteractiveImport, {
|
||||
InteractiveImportCommandOptions,
|
||||
} from 'InteractiveImport/InteractiveImport';
|
||||
@@ -59,7 +60,13 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import InteractiveImportRow from './InteractiveImportRow';
|
||||
import styles from './InteractiveImportModalContent.css';
|
||||
|
||||
type SelectType = 'select' | 'movie' | 'releaseGroup' | 'quality' | 'language';
|
||||
type SelectType =
|
||||
| 'select'
|
||||
| 'movie'
|
||||
| 'releaseGroup'
|
||||
| 'quality'
|
||||
| 'language'
|
||||
| 'indexerFlags';
|
||||
|
||||
type FilterExistingFiles = 'all' | 'new';
|
||||
|
||||
@@ -113,6 +120,15 @@ const COLUMNS = [
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'indexerFlags',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.FLAG,
|
||||
title: () => translate('IndexerFlags'),
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, {
|
||||
@@ -257,8 +273,18 @@ function InteractiveImportModalContent(
|
||||
}
|
||||
}
|
||||
|
||||
const showIndexerFlags = items.some((item) => item.indexerFlags);
|
||||
|
||||
if (!showIndexerFlags) {
|
||||
const indexerFlagsColumn = result.find((c) => c.name === 'indexerFlags');
|
||||
|
||||
if (indexerFlagsColumn) {
|
||||
indexerFlagsColumn.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [showMovie]);
|
||||
}, [showMovie, items]);
|
||||
|
||||
const selectedIds: number[] = useMemo(() => {
|
||||
return getSelectedIds(selectedState);
|
||||
@@ -283,6 +309,10 @@ function InteractiveImportModalContent(
|
||||
key: 'language',
|
||||
value: translate('SelectLanguage'),
|
||||
},
|
||||
{
|
||||
key: 'indexerFlags',
|
||||
value: translate('SelectIndexerFlags'),
|
||||
},
|
||||
];
|
||||
|
||||
if (allowMovieChange) {
|
||||
@@ -416,7 +446,14 @@ function InteractiveImportModalContent(
|
||||
const isSelected = selectedIds.indexOf(item.id) > -1;
|
||||
|
||||
if (isSelected) {
|
||||
const { movie, releaseGroup, quality, languages, movieFileId } = item;
|
||||
const {
|
||||
movie,
|
||||
releaseGroup,
|
||||
quality,
|
||||
languages,
|
||||
indexerFlags,
|
||||
movieFileId,
|
||||
} = item;
|
||||
|
||||
if (!movie) {
|
||||
setInteractiveImportErrorMessage(
|
||||
@@ -450,6 +487,7 @@ function InteractiveImportModalContent(
|
||||
releaseGroup,
|
||||
quality,
|
||||
languages,
|
||||
indexerFlags,
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -463,6 +501,7 @@ function InteractiveImportModalContent(
|
||||
releaseGroup,
|
||||
quality,
|
||||
languages,
|
||||
indexerFlags,
|
||||
downloadId,
|
||||
movieFileId,
|
||||
});
|
||||
@@ -620,6 +659,22 @@ function InteractiveImportModalContent(
|
||||
[selectedIds, dispatch]
|
||||
);
|
||||
|
||||
const onIndexerFlagsSelect = useCallback(
|
||||
(indexerFlags: number) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItems({
|
||||
ids: selectedIds,
|
||||
indexerFlags,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(reprocessInteractiveImportItems({ ids: selectedIds }));
|
||||
|
||||
setSelectModalOpen(null);
|
||||
},
|
||||
[selectedIds, dispatch]
|
||||
);
|
||||
|
||||
const errorMessage = getErrorMessage(
|
||||
error,
|
||||
translate('InteractiveImportLoadError')
|
||||
@@ -794,6 +849,14 @@ function InteractiveImportModalContent(
|
||||
onModalClose={onSelectModalClose}
|
||||
/>
|
||||
|
||||
<SelectIndexerFlagsModal
|
||||
isOpen={selectModalOpen === 'indexerFlags'}
|
||||
indexerFlags={0}
|
||||
modalTitle={modalTitle}
|
||||
onIndexerFlagsSelect={onIndexerFlagsSelect}
|
||||
onModalClose={onSelectModalClose}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isConfirmDeleteModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
|
||||
@@ -8,11 +8,13 @@ import Column from 'Components/Table/Column';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
|
||||
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
||||
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
|
||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||
import Language from 'Language/Language';
|
||||
import IndexerFlags from 'Movie/IndexerFlags';
|
||||
import Movie from 'Movie/Movie';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
@@ -30,7 +32,12 @@ import translate from 'Utilities/String/translate';
|
||||
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
|
||||
import styles from './InteractiveImportRow.css';
|
||||
|
||||
type SelectType = 'movie' | 'releaseGroup' | 'quality' | 'language';
|
||||
type SelectType =
|
||||
| 'movie'
|
||||
| 'releaseGroup'
|
||||
| 'quality'
|
||||
| 'language'
|
||||
| 'indexerFlags';
|
||||
|
||||
type SelectedChangeProps = SelectStateInputProps & {
|
||||
hasMovieFileId: boolean;
|
||||
@@ -47,6 +54,7 @@ interface InteractiveImportRowProps {
|
||||
size: number;
|
||||
customFormats?: object[];
|
||||
customFormatScore?: number;
|
||||
indexerFlags: number;
|
||||
rejections: Rejection[];
|
||||
columns: Column[];
|
||||
movieFileId?: number;
|
||||
@@ -69,6 +77,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
size,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
indexerFlags,
|
||||
rejections,
|
||||
isSelected,
|
||||
modalTitle,
|
||||
@@ -84,6 +93,10 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
() => columns.find((c) => c.name === 'movie')?.isVisible ?? false,
|
||||
[columns]
|
||||
);
|
||||
const isIndexerFlagsColumnVisible = useMemo(
|
||||
() => columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false,
|
||||
[columns]
|
||||
);
|
||||
|
||||
const [selectModalOpen, setSelectModalOpen] = useState<SelectType | null>(
|
||||
null
|
||||
@@ -223,12 +236,34 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
|
||||
);
|
||||
|
||||
const onSelectIndexerFlagsPress = useCallback(() => {
|
||||
setSelectModalOpen('indexerFlags');
|
||||
}, [setSelectModalOpen]);
|
||||
|
||||
const onIndexerFlagsSelect = useCallback(
|
||||
(indexerFlags: number) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItem({
|
||||
id,
|
||||
indexerFlags,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(reprocessInteractiveImportItems({ ids: [id] }));
|
||||
|
||||
setSelectModalOpen(null);
|
||||
selectRowAfterChange();
|
||||
},
|
||||
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
|
||||
);
|
||||
|
||||
const movieTitle = movie ? movie.title : '';
|
||||
|
||||
const showMoviePlaceholder = isSelected && !movie;
|
||||
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
||||
const showQualityPlaceholder = isSelected && !quality;
|
||||
const showLanguagePlaceholder = isSelected && !languages;
|
||||
const showIndexerFlagsPlaceholder = isSelected && !indexerFlags;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
@@ -311,6 +346,28 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
) : null}
|
||||
</TableRowCell>
|
||||
|
||||
{isIndexerFlagsColumnVisible ? (
|
||||
<TableRowCellButton
|
||||
title={translate('ClickToChangeIndexerFlags')}
|
||||
onPress={onSelectIndexerFlagsPress}
|
||||
>
|
||||
{showIndexerFlagsPlaceholder ? (
|
||||
<InteractiveImportRowCellPlaceholder isOptional={true} />
|
||||
) : (
|
||||
<>
|
||||
{indexerFlags ? (
|
||||
<Popover
|
||||
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||
title={translate('IndexerFlags')}
|
||||
body={<IndexerFlags indexerFlags={indexerFlags} />}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</TableRowCellButton>
|
||||
) : null}
|
||||
|
||||
<TableRowCell>
|
||||
{rejections.length ? (
|
||||
<Popover
|
||||
@@ -361,6 +418,14 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
onLanguagesSelect={onLanguagesSelect}
|
||||
onModalClose={onSelectModalClose}
|
||||
/>
|
||||
|
||||
<SelectIndexerFlagsModal
|
||||
isOpen={selectModalOpen === 'indexerFlags'}
|
||||
indexerFlags={indexerFlags ?? 0}
|
||||
modalTitle={modalTitle}
|
||||
onIndexerFlagsSelect={onIndexerFlagsSelect}
|
||||
onModalClose={onSelectModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface InteractiveImportCommandOptions {
|
||||
releaseGroup?: string;
|
||||
quality: QualityModel;
|
||||
languages: Language[];
|
||||
indexerFlags: number;
|
||||
downloadId?: string;
|
||||
movieFileId?: number;
|
||||
}
|
||||
@@ -27,6 +28,7 @@ interface InteractiveImport extends ModelBase {
|
||||
movie?: Movie;
|
||||
qualityWeight: number;
|
||||
customFormats: object[];
|
||||
indexerFlags: number;
|
||||
rejections: Rejection[];
|
||||
movieFileId?: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user