1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-19 17:32:38 +01:00

Fixed: Keeping search results when Missing/Cutoff Unmet repopulates

This commit is contained in:
Bogdan 2024-08-23 09:32:46 +03:00
parent bc918ed3b5
commit caf2d33c11
12 changed files with 138 additions and 318 deletions

View File

@ -1,39 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageMenuButton from 'Components/Menu/PageMenuButton';
import { align } from 'Helpers/Props';
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
import styles from './InteractiveSearch.css';
function InteractiveSearchFilterMenu(props) {
const {
selectedFilterKey,
filters,
customFilters,
onFilterSelect
} = props;
return (
<div className={styles.filterMenuContainer}>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
buttonComponent={PageMenuButton}
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
onFilterSelect={onFilterSelect}
/>
</div>
);
}
InteractiveSearchFilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
onFilterSelect: PropTypes.func.isRequired
};
export default InteractiveSearchFilterMenu;

View File

@ -1,46 +0,0 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setReleasesFilter } from 'Store/Actions/releaseActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import InteractiveSearchFilterMenu from './InteractiveSearchFilterMenu';
function createMapStateToProps(appState) {
return createSelector(
createClientSideCollectionSelector('releases'),
(releases) => {
return {
...releases
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onFilterSelect(selectedFilterKey) {
dispatch(setReleasesFilter({ selectedFilterKey }));
}
};
}
class InteractiveSearchFilterMenuConnector extends Component {
//
// Render
render() {
const {
...otherProps
} = this.props;
return (
<InteractiveSearchFilterMenu
{...otherProps}
/>
);
}
}
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchFilterMenuConnector);

View File

@ -27,7 +27,7 @@ import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import getMovieStatusDetails from 'Movie/getMovieStatusDetails';
import MovieHistoryModal from 'Movie/History/MovieHistoryModal';
import MoviePoster from 'Movie/MoviePoster';
import MovieInteractiveSearchModalConnector from 'Movie/Search/MovieInteractiveSearchModalConnector';
import MovieInteractiveSearchModal from 'Movie/Search/MovieInteractiveSearchModal';
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
@ -740,7 +740,7 @@ class MovieDetails extends Component {
onModalClose={this.onInteractiveImportModalClose}
/>
<MovieInteractiveSearchModalConnector
<MovieInteractiveSearchModal
isOpen={isInteractiveSearchModalOpen}
movieId={id}
movieTitle={title}

View File

@ -1,81 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import MovieInteractiveSearchModalConnector from './Search/MovieInteractiveSearchModalConnector';
import styles from './MovieSearchCell.css';
class MovieSearchCell extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isInteractiveSearchModalOpen: false
};
}
//
// Listeners
onManualSearchPress = () => {
this.setState({ isInteractiveSearchModalOpen: true });
};
onInteractiveSearchModalClose = () => {
this.setState({ isInteractiveSearchModalOpen: false });
};
//
// Render
render() {
const {
movieId,
movieTitle,
isSearching,
onSearchPress,
...otherProps
} = this.props;
return (
<TableRowCell className={styles.movieSearchCell}>
<SpinnerIconButton
name={icons.SEARCH}
isSpinning={isSearching}
onPress={onSearchPress}
title={translate('AutomaticSearch')}
/>
<IconButton
name={icons.INTERACTIVE}
onPress={this.onManualSearchPress}
title={translate('InteractiveSearch')}
/>
<MovieInteractiveSearchModalConnector
isOpen={this.state.isInteractiveSearchModalOpen}
movieId={movieId}
movieTitle={movieTitle}
onModalClose={this.onInteractiveSearchModalClose}
{...otherProps}
/>
</TableRowCell>
);
}
}
MovieSearchCell.propTypes = {
movieId: PropTypes.number.isRequired,
movieTitle: PropTypes.string.isRequired,
isSearching: PropTypes.bool.isRequired,
onSearchPress: PropTypes.func.isRequired
};
export default MovieSearchCell;

View File

@ -0,0 +1,71 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { MOVIE_SEARCH } from 'Commands/commandNames';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
import { icons } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
import translate from 'Utilities/String/translate';
import MovieInteractiveSearchModal from './Search/MovieInteractiveSearchModal';
import styles from './MovieSearchCell.css';
interface MovieSearchCellProps {
movieId: number;
movieTitle: string;
}
function MovieSearchCell(props: MovieSearchCellProps) {
const { movieId, movieTitle } = props;
const executingCommands = useSelector(createExecutingCommandsSelector());
const isSearching = executingCommands.some(({ name, body }) => {
const { movieIds = [] } = body;
return name === MOVIE_SEARCH && movieIds.indexOf(movieId) > -1;
});
const dispatch = useDispatch();
const [
isInteractiveSearchModalOpen,
setInteractiveSearchModalOpen,
setInteractiveSearchModalClosed,
] = useModalOpenState(false);
const handleSearchPress = useCallback(() => {
dispatch(
executeCommand({
name: MOVIE_SEARCH,
movieIds: [movieId],
})
);
}, [movieId, dispatch]);
return (
<TableRowCell className={styles.movieSearchCell}>
<SpinnerIconButton
name={icons.SEARCH}
isSpinning={isSearching}
title={translate('AutomaticSearch')}
onPress={handleSearchPress}
/>
<IconButton
name={icons.INTERACTIVE}
title={translate('InteractiveSearch')}
onPress={setInteractiveSearchModalOpen}
/>
<MovieInteractiveSearchModal
isOpen={isInteractiveSearchModalOpen}
movieId={movieId}
movieTitle={movieTitle}
onModalClose={setInteractiveSearchModalClosed}
/>
</TableRowCell>
);
}
export default MovieSearchCell;

View File

@ -1,48 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import MovieSearchCell from 'Movie/MovieSearchCell';
import { executeCommand } from 'Store/Actions/commandActions';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import { isCommandExecuting } from 'Utilities/Command';
function createMapStateToProps() {
return createSelector(
(state, { movieId }) => movieId,
createMovieSelector(),
createCommandsSelector(),
(movieId, movie, commands) => {
const isSearching = commands.some((command) => {
const movieSearch = command.name === commandNames.MOVIE_SEARCH;
if (!movieSearch) {
return false;
}
return (
isCommandExecuting(command) &&
command.body.movieIds.indexOf(movieId) > -1
);
});
return {
movieMonitored: movie.monitored,
isSearching
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onSearchPress(name, path) {
dispatch(executeCommand({
name: commandNames.MOVIE_SEARCH,
movieIds: [props.movieId]
}));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieSearchCell);

View File

@ -1,38 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import MovieInteractiveSearchModalContent from './MovieInteractiveSearchModalContent';
function MovieInteractiveSearchModal(props) {
const {
isOpen,
movieId,
movieTitle,
onModalClose
} = props;
return (
<Modal
isOpen={isOpen}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
size={sizes.EXTRA_EXTRA_LARGE}
>
<MovieInteractiveSearchModalContent
movieId={movieId}
movieTitle={movieTitle}
onModalClose={onModalClose}
/>
</Modal>
);
}
MovieInteractiveSearchModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
movieId: PropTypes.number.isRequired,
movieTitle: PropTypes.string,
onModalClose: PropTypes.func.isRequired
};
export default MovieInteractiveSearchModal;

View File

@ -0,0 +1,46 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import {
cancelFetchReleases,
clearReleases,
} from 'Store/Actions/releaseActions';
import MovieInteractiveSearchModalContent from './MovieInteractiveSearchModalContent';
interface MovieInteractiveSearchModalProps {
isOpen: boolean;
movieId: number;
movieTitle?: string;
onModalClose(): void;
}
function MovieInteractiveSearchModal(props: MovieInteractiveSearchModalProps) {
const { isOpen, movieId, movieTitle, onModalClose } = props;
const dispatch = useDispatch();
const handleModalClose = useCallback(() => {
dispatch(cancelFetchReleases());
dispatch(clearReleases());
onModalClose();
}, [dispatch, onModalClose]);
return (
<Modal
isOpen={isOpen}
closeOnBackgroundClick={false}
size={sizes.EXTRA_EXTRA_LARGE}
onModalClose={handleModalClose}
>
<MovieInteractiveSearchModalContent
movieId={movieId}
movieTitle={movieTitle}
onModalClose={handleModalClose}
/>
</Modal>
);
}
export default MovieInteractiveSearchModal;

View File

@ -1,59 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
import MovieInteractiveSearchModal from './MovieInteractiveSearchModal';
function createMapDispatchToProps(dispatch, props) {
return {
dispatchCancelFetchReleases() {
dispatch(cancelFetchReleases());
},
dispatchClearReleases() {
dispatch(clearReleases());
},
onModalClose() {
dispatch(cancelFetchReleases());
dispatch(clearReleases());
props.onModalClose();
}
};
}
class MovieInteractiveSearchModalConnector extends Component {
//
// Lifecycle
componentWillUnmount() {
this.props.dispatchCancelFetchReleases();
this.props.dispatchClearReleases();
}
//
// Render
render() {
const {
dispatchCancelFetchReleases,
dispatchClearReleases,
...otherProps
} = this.props;
return (
<MovieInteractiveSearchModal
{...otherProps}
/>
);
}
}
MovieInteractiveSearchModalConnector.propTypes = {
...MovieInteractiveSearchModal.propTypes,
dispatchCancelFetchReleases: PropTypes.func.isRequired,
dispatchClearReleases: PropTypes.func.isRequired
};
export default connect(null, createMapDispatchToProps)(MovieInteractiveSearchModalConnector);

View File

@ -1,4 +1,5 @@
import React from 'react';
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
@ -6,6 +7,10 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { scrollDirections } from 'Helpers/Props';
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
import {
cancelFetchReleases,
clearReleases,
} from 'Store/Actions/releaseActions';
import translate from 'Utilities/String/translate';
interface MovieInteractiveSearchModalContentProps {
@ -19,6 +24,15 @@ function MovieInteractiveSearchModalContent(
) {
const { movieId, movieTitle, onModalClose } = props;
const dispatch = useDispatch();
useEffect(() => {
return () => {
dispatch(cancelFetchReleases());
dispatch(clearReleases());
};
}, [dispatch]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>

View File

@ -5,7 +5,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import movieEntities from 'Movie/movieEntities';
import MovieSearchCellConnector from 'Movie/MovieSearchCellConnector';
import MovieSearchCell from 'Movie/MovieSearchCell';
import MovieStatusConnector from 'Movie/MovieStatusConnector';
import MovieTitleLink from 'Movie/MovieTitleLink';
import MovieFileLanguageConnector from 'MovieFile/MovieFileLanguageConnector';
@ -124,7 +124,7 @@ function CutoffUnmetRow(props) {
if (name === 'actions') {
return (
<MovieSearchCellConnector
<MovieSearchCell
key={name}
movieId={id}
movieTitle={title}

View File

@ -5,7 +5,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import movieEntities from 'Movie/movieEntities';
import MovieSearchCellConnector from 'Movie/MovieSearchCellConnector';
import MovieSearchCell from 'Movie/MovieSearchCell';
import MovieStatusConnector from 'Movie/MovieStatusConnector';
import MovieTitleLink from 'Movie/MovieTitleLink';
import styles from './MissingRow.css';
@ -114,7 +114,7 @@ function MissingRow(props) {
if (name === 'actions') {
return (
<MovieSearchCellConnector
<MovieSearchCell
key={name}
movieId={id}
movieTitle={title}