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:
parent
bc918ed3b5
commit
caf2d33c11
@ -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;
|
@ -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);
|
@ -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}
|
||||
|
@ -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;
|
71
frontend/src/Movie/MovieSearchCell.tsx
Normal file
71
frontend/src/Movie/MovieSearchCell.tsx
Normal 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;
|
@ -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);
|
@ -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;
|
46
frontend/src/Movie/Search/MovieInteractiveSearchModal.tsx
Normal file
46
frontend/src/Movie/Search/MovieInteractiveSearchModal.tsx
Normal 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;
|
@ -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);
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user