mirror of
https://github.com/Radarr/Radarr
synced 2025-02-23 14:51:17 +00:00
New: Added filter and sort options to Collections (#8731)
* New: Added filter and sort options to Collections * Add AllMovieWithCollectionsTmdbIds method to MovieService and MovieRepository
This commit is contained in:
parent
fed98a648f
commit
cbae355402
7 changed files with 62 additions and 19 deletions
|
@ -2,17 +2,12 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
|
||||||
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
|
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createCollectionSelector(),
|
createCollectionSelector(),
|
||||||
createAllMoviesSelector(),
|
(collection) => {
|
||||||
(
|
|
||||||
collection,
|
|
||||||
allMovies
|
|
||||||
) => {
|
|
||||||
// If a movie is deleted this selector may fire before the parent
|
// If a movie is deleted this selector may fire before the parent
|
||||||
// selecors, which will result in an undefined movie, if that happens
|
// selecors, which will result in an undefined movie, if that happens
|
||||||
// we want to return early here and again in the render function to avoid
|
// we want to return early here and again in the render function to avoid
|
||||||
|
@ -22,21 +17,11 @@ function createMapStateToProps() {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let allGenres = [];
|
const allGenres = collection.movies.flatMap((movie) => movie.genres);
|
||||||
let libraryMovies = 0;
|
|
||||||
|
|
||||||
collection.movies.forEach((movie) => {
|
|
||||||
allGenres = allGenres.concat(movie.genres);
|
|
||||||
|
|
||||||
if (allMovies.find((libraryMovie) => libraryMovie.tmdbId === movie.tmdbId)) {
|
|
||||||
libraryMovies++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...collection,
|
...collection,
|
||||||
genres: Array.from(new Set(allGenres)).slice(0, 3),
|
genres: Array.from(new Set(allGenres)).slice(0, 3)
|
||||||
missingMovies: collection.movies.length - libraryMovies
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,6 +28,14 @@ function CollectionSortMenu(props) {
|
||||||
>
|
>
|
||||||
{translate('Title')}
|
{translate('Title')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
<SortMenuItem
|
||||||
|
name="missingMovies"
|
||||||
|
sortKey={sortKey}
|
||||||
|
sortDirection={sortDirection}
|
||||||
|
onPress={onSortSelect}
|
||||||
|
>
|
||||||
|
{translate('Missing')}
|
||||||
|
</SortMenuItem>
|
||||||
</MenuContent>
|
</MenuContent>
|
||||||
</SortMenu>
|
</SortMenu>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { batchActions } from 'redux-batched-actions';
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
|
||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
|
@ -62,6 +62,28 @@ export const defaultState = {
|
||||||
key: 'all',
|
key: 'all',
|
||||||
label: 'All',
|
label: 'All',
|
||||||
filters: []
|
filters: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'missing',
|
||||||
|
label: 'Missing',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'missingMovies',
|
||||||
|
value: 0,
|
||||||
|
type: filterTypes.GREATER_THAN
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'complete',
|
||||||
|
label: 'Complete',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'missingMovies',
|
||||||
|
value: 0,
|
||||||
|
type: filterTypes.EQUAL
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ public interface IMovieRepository : IBasicRepository<Movie>
|
||||||
Dictionary<int, List<int>> AllMovieTags();
|
Dictionary<int, List<int>> AllMovieTags();
|
||||||
List<int> GetRecommendations();
|
List<int> GetRecommendations();
|
||||||
bool ExistsByMetadataId(int metadataId);
|
bool ExistsByMetadataId(int metadataId);
|
||||||
|
HashSet<int> AllMovieWithCollectionsTmdbIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
|
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
|
||||||
|
@ -373,5 +374,13 @@ public bool ExistsByMetadataId(int metadataId)
|
||||||
|
|
||||||
return movies.Any();
|
return movies.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashSet<int> AllMovieWithCollectionsTmdbIds()
|
||||||
|
{
|
||||||
|
using (var conn = _database.OpenConnection())
|
||||||
|
{
|
||||||
|
return conn.Query<int>("SELECT \"TmdbId\" FROM \"MovieMetadata\" JOIN \"Movies\" ON (\"Movies\".\"MovieMetadataId\" = \"MovieMetadata\".\"Id\") WHERE \"CollectionTmdbId\" > 0").ToHashSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ public interface IMovieService
|
||||||
bool MoviePathExists(string folder);
|
bool MoviePathExists(string folder);
|
||||||
void RemoveAddOptions(Movie movie);
|
void RemoveAddOptions(Movie movie);
|
||||||
bool ExistsByMetadataId(int metadataId);
|
bool ExistsByMetadataId(int metadataId);
|
||||||
|
HashSet<int> AllMovieWithCollectionsTmdbIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>,
|
public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>,
|
||||||
|
@ -390,6 +391,11 @@ public bool ExistsByMetadataId(int metadataId)
|
||||||
return _movieRepository.ExistsByMetadataId(metadataId);
|
return _movieRepository.ExistsByMetadataId(metadataId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashSet<int> AllMovieWithCollectionsTmdbIds()
|
||||||
|
{
|
||||||
|
return _movieRepository.AllMovieWithCollectionsTmdbIds();
|
||||||
|
}
|
||||||
|
|
||||||
private Movie ReturnSingleMovieOrThrow(List<Movie> movies)
|
private Movie ReturnSingleMovieOrThrow(List<Movie> movies)
|
||||||
{
|
{
|
||||||
if (movies.Count == 0)
|
if (movies.Count == 0)
|
||||||
|
|
|
@ -135,6 +135,7 @@ private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> coll
|
||||||
// Avoid calling for naming spec on every movie in filenamebuilder
|
// Avoid calling for naming spec on every movie in filenamebuilder
|
||||||
var namingConfig = _namingService.GetConfig();
|
var namingConfig = _namingService.GetConfig();
|
||||||
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
|
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
|
||||||
|
var existingMoviesTmdbIds = _movieService.AllMovieWithCollectionsTmdbIds();
|
||||||
|
|
||||||
foreach (var collection in collections)
|
foreach (var collection in collections)
|
||||||
{
|
{
|
||||||
|
@ -145,6 +146,11 @@ private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> coll
|
||||||
var movieResource = movie.ToResource();
|
var movieResource = movie.ToResource();
|
||||||
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie }, namingConfig);
|
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie }, namingConfig);
|
||||||
|
|
||||||
|
if (!existingMoviesTmdbIds.Contains(movie.TmdbId))
|
||||||
|
{
|
||||||
|
resource.MissingMovies++;
|
||||||
|
}
|
||||||
|
|
||||||
resource.Movies.Add(movieResource);
|
resource.Movies.Add(movieResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,12 +161,18 @@ private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> coll
|
||||||
private CollectionResource MapToResource(MovieCollection collection)
|
private CollectionResource MapToResource(MovieCollection collection)
|
||||||
{
|
{
|
||||||
var resource = collection.ToResource();
|
var resource = collection.ToResource();
|
||||||
|
var existingMoviesTmdbIds = _movieService.AllMovieWithCollectionsTmdbIds();
|
||||||
|
|
||||||
foreach (var movie in _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId))
|
foreach (var movie in _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId))
|
||||||
{
|
{
|
||||||
var movieResource = movie.ToResource();
|
var movieResource = movie.ToResource();
|
||||||
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie });
|
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie });
|
||||||
|
|
||||||
|
if (!existingMoviesTmdbIds.Contains(movie.TmdbId))
|
||||||
|
{
|
||||||
|
resource.MissingMovies++;
|
||||||
|
}
|
||||||
|
|
||||||
resource.Movies.Add(movieResource);
|
resource.Movies.Add(movieResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ public CollectionResource()
|
||||||
public bool SearchOnAdd { get; set; }
|
public bool SearchOnAdd { get; set; }
|
||||||
public MovieStatusType MinimumAvailability { get; set; }
|
public MovieStatusType MinimumAvailability { get; set; }
|
||||||
public List<CollectionMovieResource> Movies { get; set; }
|
public List<CollectionMovieResource> Movies { get; set; }
|
||||||
|
public int MissingMovies { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CollectionResourceMapper
|
public static class CollectionResourceMapper
|
||||||
|
|
Loading…
Reference in a new issue