diff --git a/frontend/src/MovieFile/Editor/MovieFileEditorTableContent.js b/frontend/src/MovieFile/Editor/MovieFileEditorTableContent.js
index 259f8c935..40133755c 100644
--- a/frontend/src/MovieFile/Editor/MovieFileEditorTableContent.js
+++ b/frontend/src/MovieFile/Editor/MovieFileEditorTableContent.js
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
+import { sortDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import MovieFileEditorRow from './MovieFileEditorRow';
import styles from './MovieFileEditorTableContent.css';
@@ -15,6 +16,9 @@ class MovieFileEditorTableContent extends Component {
const {
items,
columns,
+ sortKey,
+ sortDirection,
+ onSortPress,
onTableOptionChange
} = this.props;
@@ -31,6 +35,9 @@ class MovieFileEditorTableContent extends Component {
!!items.length &&
@@ -60,7 +67,10 @@ MovieFileEditorTableContent.propTypes = {
isDeleting: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
+ sortKey: PropTypes.string.isRequired,
+ sortDirection: PropTypes.oneOf(sortDirections.all),
onTableOptionChange: PropTypes.func.isRequired,
+ onSortPress: PropTypes.func.isRequired,
onDeletePress: PropTypes.func.isRequired
};
diff --git a/frontend/src/MovieFile/Editor/MovieFileEditorTableContentConnector.js b/frontend/src/MovieFile/Editor/MovieFileEditorTableContentConnector.js
index b3e190228..3d19a4bee 100644
--- a/frontend/src/MovieFile/Editor/MovieFileEditorTableContentConnector.js
+++ b/frontend/src/MovieFile/Editor/MovieFileEditorTableContentConnector.js
@@ -2,8 +2,9 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
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 createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import getQualities from 'Utilities/Quality/getQualities';
import MovieFileEditorTableContent from './MovieFileEditorTableContent';
@@ -11,7 +12,7 @@ import MovieFileEditorTableContent from './MovieFileEditorTableContent';
function createMapStateToProps() {
return createSelector(
(state, { movieId }) => movieId,
- (state) => state.movieFiles,
+ createClientSideCollectionSelector('movieFiles'),
(state) => state.settings.languages,
(state) => state.settings.qualityProfiles,
createMovieSelector(),
@@ -28,6 +29,8 @@ function createMapStateToProps() {
return {
items: filesForMovie,
columns: movieFiles.columns,
+ sortKey: movieFiles.sortKey,
+ sortDirection: movieFiles.sortDirection,
isDeleting: movieFiles.isDeleting,
isSaving: movieFiles.isSaving,
error: null,
@@ -38,31 +41,13 @@ function createMapStateToProps() {
);
}
-function createMapDispatchToProps(dispatch, props) {
- return {
- dispatchFetchQualityProfileSchema() {
- dispatch(fetchQualityProfileSchema());
- },
-
- dispatchFetchLanguages() {
- dispatch(fetchLanguages());
- },
-
- dispatchUpdateMovieFiles(updateProps) {
- dispatch(updateMovieFiles(updateProps));
- },
-
- onTableOptionChange(payload) {
- dispatch(setMovieFilesTableOption(payload));
- },
-
- onDeletePress(movieFileId) {
- dispatch(deleteMovieFile({
- id: movieFileId
- }));
- }
- };
-}
+const mapDispatchToProps = {
+ fetchQualityProfileSchema,
+ fetchLanguages,
+ deleteMovieFile,
+ setMovieFilesTableOption,
+ setMovieFilesSort
+};
class MovieFileEditorTableContentConnector extends Component {
@@ -70,24 +55,40 @@ class MovieFileEditorTableContentConnector extends Component {
// Lifecycle
componentDidMount() {
- this.props.dispatchFetchLanguages();
- this.props.dispatchFetchQualityProfileSchema();
+ this.props.fetchLanguages();
+ 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() {
- const {
- dispatchFetchLanguages,
- dispatchFetchQualityProfileSchema,
- dispatchUpdateMovieFiles,
- ...otherProps
- } = this.props;
-
return (
);
}
@@ -97,9 +98,11 @@ MovieFileEditorTableContentConnector.propTypes = {
movieId: PropTypes.number.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
qualities: PropTypes.arrayOf(PropTypes.object).isRequired,
- dispatchFetchLanguages: PropTypes.func.isRequired,
- dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
- dispatchUpdateMovieFiles: PropTypes.func.isRequired
+ fetchLanguages: PropTypes.func.isRequired,
+ fetchQualityProfileSchema: 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);
diff --git a/frontend/src/Store/Actions/movieFileActions.js b/frontend/src/Store/Actions/movieFileActions.js
index 609cb67f2..238a85d5d 100644
--- a/frontend/src/Store/Actions/movieFileActions.js
+++ b/frontend/src/Store/Actions/movieFileActions.js
@@ -4,8 +4,9 @@ import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
-import { icons } from 'Helpers/Props';
+import { icons, sortDirections } from 'Helpers/Props';
import movieEntities from 'Movie/movieEntities';
+import createSetClientSideCollectionSortReducer from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetTableOptionReducer from 'Store/Actions/Creators/Reducers/createSetTableOptionReducer';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
@@ -31,13 +32,16 @@ export const defaultState = {
deleteError: null,
isSaving: false,
saveError: null,
+ sortKey: 'relativePath',
+ sortDirection: sortDirections.ASCENDING,
items: [],
columns: [
{
name: 'relativePath',
label: () => translate('RelativePath'),
- isVisible: true
+ isVisible: true,
+ isSortable: true
},
{
name: 'videoCodec',
@@ -67,7 +71,8 @@ export const defaultState = {
{
name: 'size',
label: () => translate('Size'),
- isVisible: true
+ isVisible: true,
+ isSortable: true
},
{
name: 'languages',
@@ -96,12 +101,14 @@ export const defaultState = {
name: icons.SCORE,
title: () => translate('CustomFormatScore')
}),
- isVisible: true
+ isVisible: true,
+ isSortable: true
},
{
name: 'dateAdded',
label: () => translate('Added'),
- isVisible: false
+ isVisible: false,
+ isSortable: true
},
{
name: 'actions',
@@ -114,7 +121,9 @@ export const defaultState = {
};
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 UPDATE_MOVIE_FILES = 'movieFiles/updateMovieFiles';
export const CLEAR_MOVIE_FILES = 'movieFiles/clearMovieFiles';
+export const SET_MOVIE_FILES_SORT = 'movieFiles/setMovieFilesSort';
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 updateMovieFiles = createThunk(UPDATE_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);
//
@@ -327,6 +338,7 @@ export const actionHandlers = handleThunks({
// Reducers
export const reducers = createHandleActions({
+
[SET_MOVIE_FILES_TABLE_OPTION]: createSetTableOptionReducer(section),
[CLEAR_MOVIE_FILES]: (state) => {
@@ -340,6 +352,8 @@ export const reducers = createHandleActions({
saveError: null,
items: []
});
- }
+ },
+
+ [SET_MOVIE_FILES_SORT]: createSetClientSideCollectionSortReducer(section)
}, defaultState, section);
diff --git a/src/Radarr.Api.V3/ExtraFiles/ExtraFileController.cs b/src/Radarr.Api.V3/ExtraFiles/ExtraFileController.cs
index 4d8ec87d3..fb3d649e1 100644
--- a/src/Radarr.Api.V3/ExtraFiles/ExtraFileController.cs
+++ b/src/Radarr.Api.V3/ExtraFiles/ExtraFileController.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Extras.Metadata.Files;
@@ -27,9 +28,9 @@ public List GetFiles(int movieId)
{
var extraFiles = new List();
- var subtitleFiles = _subtitleFileService.GetFilesByMovie(movieId);
- var metadataFiles = _metadataFileService.GetFilesByMovie(movieId);
- var otherExtraFiles = _otherFileService.GetFilesByMovie(movieId);
+ var subtitleFiles = _subtitleFileService.GetFilesByMovie(movieId).OrderBy(f => f.RelativePath).ToList();
+ var metadataFiles = _metadataFileService.GetFilesByMovie(movieId).OrderBy(f => f.RelativePath).ToList();
+ var otherExtraFiles = _otherFileService.GetFilesByMovie(movieId).OrderBy(f => f.RelativePath).ToList();
extraFiles.AddRange(subtitleFiles.ToResource());
extraFiles.AddRange(metadataFiles.ToResource());