Sort movie files on movie details page

(cherry picked from commit 113b0864b8e92b7b768acc8341bdf4c9e2e1a47f)
This commit is contained in:
Mark McDowall 2023-10-06 12:38:10 -07:00 committed by Bogdan
parent 4a9c0b2240
commit b08981dee0
4 changed files with 79 additions and 51 deletions

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import { sortDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import MovieFileEditorRow from './MovieFileEditorRow'; import MovieFileEditorRow from './MovieFileEditorRow';
import styles from './MovieFileEditorTableContent.css'; import styles from './MovieFileEditorTableContent.css';
@ -15,6 +16,9 @@ class MovieFileEditorTableContent extends Component {
const { const {
items, items,
columns, columns,
sortKey,
sortDirection,
onSortPress,
onTableOptionChange onTableOptionChange
} = this.props; } = this.props;
@ -31,6 +35,9 @@ class MovieFileEditorTableContent extends Component {
!!items.length && !!items.length &&
<Table <Table
columns={columns} columns={columns}
sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
onTableOptionChange={onTableOptionChange} onTableOptionChange={onTableOptionChange}
> >
<TableBody> <TableBody>
@ -60,7 +67,10 @@ MovieFileEditorTableContent.propTypes = {
isDeleting: PropTypes.bool.isRequired, isDeleting: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string.isRequired,
sortDirection: PropTypes.oneOf(sortDirections.all),
onTableOptionChange: PropTypes.func.isRequired, onTableOptionChange: PropTypes.func.isRequired,
onSortPress: PropTypes.func.isRequired,
onDeletePress: PropTypes.func.isRequired onDeletePress: PropTypes.func.isRequired
}; };

View File

@ -2,8 +2,9 @@ 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 { deleteMovieFile, setMovieFilesTableOption, updateMovieFiles } from 'Store/Actions/movieFileActions'; import { deleteMovieFile, setMovieFilesSort, setMovieFilesTableOption } from 'Store/Actions/movieFileActions';
import { fetchLanguages, fetchQualityProfileSchema } from 'Store/Actions/settingsActions'; import { fetchLanguages, fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createMovieSelector from 'Store/Selectors/createMovieSelector'; import createMovieSelector from 'Store/Selectors/createMovieSelector';
import getQualities from 'Utilities/Quality/getQualities'; import getQualities from 'Utilities/Quality/getQualities';
import MovieFileEditorTableContent from './MovieFileEditorTableContent'; import MovieFileEditorTableContent from './MovieFileEditorTableContent';
@ -11,7 +12,7 @@ import MovieFileEditorTableContent from './MovieFileEditorTableContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { movieId }) => movieId, (state, { movieId }) => movieId,
(state) => state.movieFiles, createClientSideCollectionSelector('movieFiles'),
(state) => state.settings.languages, (state) => state.settings.languages,
(state) => state.settings.qualityProfiles, (state) => state.settings.qualityProfiles,
createMovieSelector(), createMovieSelector(),
@ -28,6 +29,8 @@ function createMapStateToProps() {
return { return {
items: filesForMovie, items: filesForMovie,
columns: movieFiles.columns, columns: movieFiles.columns,
sortKey: movieFiles.sortKey,
sortDirection: movieFiles.sortDirection,
isDeleting: movieFiles.isDeleting, isDeleting: movieFiles.isDeleting,
isSaving: movieFiles.isSaving, isSaving: movieFiles.isSaving,
error: null, error: null,
@ -38,31 +41,13 @@ function createMapStateToProps() {
); );
} }
function createMapDispatchToProps(dispatch, props) { const mapDispatchToProps = {
return { fetchQualityProfileSchema,
dispatchFetchQualityProfileSchema() { fetchLanguages,
dispatch(fetchQualityProfileSchema()); deleteMovieFile,
}, setMovieFilesTableOption,
setMovieFilesSort
dispatchFetchLanguages() { };
dispatch(fetchLanguages());
},
dispatchUpdateMovieFiles(updateProps) {
dispatch(updateMovieFiles(updateProps));
},
onTableOptionChange(payload) {
dispatch(setMovieFilesTableOption(payload));
},
onDeletePress(movieFileId) {
dispatch(deleteMovieFile({
id: movieFileId
}));
}
};
}
class MovieFileEditorTableContentConnector extends Component { class MovieFileEditorTableContentConnector extends Component {
@ -70,24 +55,40 @@ class MovieFileEditorTableContentConnector extends Component {
// Lifecycle // Lifecycle
componentDidMount() { componentDidMount() {
this.props.dispatchFetchLanguages(); this.props.fetchLanguages();
this.props.dispatchFetchQualityProfileSchema(); this.props.fetchQualityProfileSchema();
} }
//
// Listeners
onDeletePress = (movieFileId) => {
this.props.deleteMovieFile({
id: movieFileId
});
};
onTableOptionChange = (payload) => {
this.props.setMovieFilesTableOption(payload);
};
onSortPress = (sortKey, sortDirection) => {
this.props.setMovieFilesSort({
sortKey,
sortDirection
});
};
// //
// Render // Render
render() { render() {
const {
dispatchFetchLanguages,
dispatchFetchQualityProfileSchema,
dispatchUpdateMovieFiles,
...otherProps
} = this.props;
return ( return (
<MovieFileEditorTableContent <MovieFileEditorTableContent
{...otherProps} {...this.props}
onDeletePress={this.onDeletePress}
onTableOptionChange={this.onTableOptionChange}
onSortPress={this.onSortPress}
/> />
); );
} }
@ -97,9 +98,11 @@ MovieFileEditorTableContentConnector.propTypes = {
movieId: PropTypes.number.isRequired, movieId: PropTypes.number.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
qualities: PropTypes.arrayOf(PropTypes.object).isRequired, qualities: PropTypes.arrayOf(PropTypes.object).isRequired,
dispatchFetchLanguages: PropTypes.func.isRequired, fetchLanguages: PropTypes.func.isRequired,
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired, fetchQualityProfileSchema: PropTypes.func.isRequired,
dispatchUpdateMovieFiles: PropTypes.func.isRequired deleteMovieFile: PropTypes.func.isRequired,
setMovieFilesTableOption: PropTypes.func.isRequired,
setMovieFilesSort: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieFileEditorTableContentConnector); export default connect(createMapStateToProps, mapDispatchToProps)(MovieFileEditorTableContentConnector);

View File

@ -4,8 +4,9 @@ import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions'; import { batchActions } from 'redux-batched-actions';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import { icons } from 'Helpers/Props'; import { icons, sortDirections } from 'Helpers/Props';
import movieEntities from 'Movie/movieEntities'; import movieEntities from 'Movie/movieEntities';
import createSetClientSideCollectionSortReducer from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetTableOptionReducer from 'Store/Actions/Creators/Reducers/createSetTableOptionReducer'; import createSetTableOptionReducer from 'Store/Actions/Creators/Reducers/createSetTableOptionReducer';
import { createThunk, handleThunks } from 'Store/thunks'; import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
@ -31,13 +32,16 @@ export const defaultState = {
deleteError: null, deleteError: null,
isSaving: false, isSaving: false,
saveError: null, saveError: null,
sortKey: 'relativePath',
sortDirection: sortDirections.ASCENDING,
items: [], items: [],
columns: [ columns: [
{ {
name: 'relativePath', name: 'relativePath',
label: () => translate('RelativePath'), label: () => translate('RelativePath'),
isVisible: true isVisible: true,
isSortable: true
}, },
{ {
name: 'videoCodec', name: 'videoCodec',
@ -67,7 +71,8 @@ export const defaultState = {
{ {
name: 'size', name: 'size',
label: () => translate('Size'), label: () => translate('Size'),
isVisible: true isVisible: true,
isSortable: true
}, },
{ {
name: 'languages', name: 'languages',
@ -96,12 +101,14 @@ export const defaultState = {
name: icons.SCORE, name: icons.SCORE,
title: () => translate('CustomFormatScore') title: () => translate('CustomFormatScore')
}), }),
isVisible: true isVisible: true,
isSortable: true
}, },
{ {
name: 'dateAdded', name: 'dateAdded',
label: () => translate('Added'), label: () => translate('Added'),
isVisible: false isVisible: false,
isSortable: true
}, },
{ {
name: 'actions', name: 'actions',
@ -114,7 +121,9 @@ export const defaultState = {
}; };
export const persistState = [ export const persistState = [
'movieFiles.columns' 'movieFiles.columns',
'movieFiles.sortDirection',
'movieFiles.sortKey'
]; ];
// //
@ -125,6 +134,7 @@ export const DELETE_MOVIE_FILE = 'movieFiles/deleteMovieFile';
export const DELETE_MOVIE_FILES = 'movieFiles/deleteMovieFiles'; export const DELETE_MOVIE_FILES = 'movieFiles/deleteMovieFiles';
export const UPDATE_MOVIE_FILES = 'movieFiles/updateMovieFiles'; export const UPDATE_MOVIE_FILES = 'movieFiles/updateMovieFiles';
export const CLEAR_MOVIE_FILES = 'movieFiles/clearMovieFiles'; export const CLEAR_MOVIE_FILES = 'movieFiles/clearMovieFiles';
export const SET_MOVIE_FILES_SORT = 'movieFiles/setMovieFilesSort';
export const SET_MOVIE_FILES_TABLE_OPTION = 'movieFiles/setMovieFilesTableOption'; export const SET_MOVIE_FILES_TABLE_OPTION = 'movieFiles/setMovieFilesTableOption';
// //
@ -135,6 +145,7 @@ export const deleteMovieFile = createThunk(DELETE_MOVIE_FILE);
export const deleteMovieFiles = createThunk(DELETE_MOVIE_FILES); export const deleteMovieFiles = createThunk(DELETE_MOVIE_FILES);
export const updateMovieFiles = createThunk(UPDATE_MOVIE_FILES); export const updateMovieFiles = createThunk(UPDATE_MOVIE_FILES);
export const clearMovieFiles = createAction(CLEAR_MOVIE_FILES); export const clearMovieFiles = createAction(CLEAR_MOVIE_FILES);
export const setMovieFilesSort = createAction(SET_MOVIE_FILES_SORT);
export const setMovieFilesTableOption = createAction(SET_MOVIE_FILES_TABLE_OPTION); export const setMovieFilesTableOption = createAction(SET_MOVIE_FILES_TABLE_OPTION);
// //
@ -327,6 +338,7 @@ export const actionHandlers = handleThunks({
// Reducers // Reducers
export const reducers = createHandleActions({ export const reducers = createHandleActions({
[SET_MOVIE_FILES_TABLE_OPTION]: createSetTableOptionReducer(section), [SET_MOVIE_FILES_TABLE_OPTION]: createSetTableOptionReducer(section),
[CLEAR_MOVIE_FILES]: (state) => { [CLEAR_MOVIE_FILES]: (state) => {
@ -340,6 +352,8 @@ export const reducers = createHandleActions({
saveError: null, saveError: null,
items: [] items: []
}); });
} },
[SET_MOVIE_FILES_SORT]: createSetClientSideCollectionSortReducer(section)
}, defaultState, section); }, defaultState, section);

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Extras.Files; using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Extras.Metadata.Files; using NzbDrone.Core.Extras.Metadata.Files;
@ -27,9 +28,9 @@ namespace Radarr.Api.V3.ExtraFiles
{ {
var extraFiles = new List<ExtraFileResource>(); var extraFiles = new List<ExtraFileResource>();
var subtitleFiles = _subtitleFileService.GetFilesByMovie(movieId); var subtitleFiles = _subtitleFileService.GetFilesByMovie(movieId).OrderBy(f => f.RelativePath).ToList();
var metadataFiles = _metadataFileService.GetFilesByMovie(movieId); var metadataFiles = _metadataFileService.GetFilesByMovie(movieId).OrderBy(f => f.RelativePath).ToList();
var otherExtraFiles = _otherFileService.GetFilesByMovie(movieId); var otherExtraFiles = _otherFileService.GetFilesByMovie(movieId).OrderBy(f => f.RelativePath).ToList();
extraFiles.AddRange(subtitleFiles.ToResource()); extraFiles.AddRange(subtitleFiles.ToResource());
extraFiles.AddRange(metadataFiles.ToResource()); extraFiles.AddRange(metadataFiles.ToResource());