mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
New: Show ExtraFiles in UI
This commit is contained in:
@@ -25,6 +25,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
|||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
||||||
|
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
|
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
@@ -182,6 +183,7 @@ class MovieDetails extends Component {
|
|||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
movieFilesError,
|
movieFilesError,
|
||||||
movieCreditsError,
|
movieCreditsError,
|
||||||
|
extraFilesError,
|
||||||
hasMovieFiles,
|
hasMovieFiles,
|
||||||
previousMovie,
|
previousMovie,
|
||||||
nextMovie,
|
nextMovie,
|
||||||
@@ -457,15 +459,25 @@ class MovieDetails extends Component {
|
|||||||
|
|
||||||
<div className={styles.contentContainer}>
|
<div className={styles.contentContainer}>
|
||||||
{
|
{
|
||||||
!isPopulated && !movieFilesError && !movieCreditsError &&
|
!isPopulated && !movieFilesError && !movieCreditsError && !extraFilesError &&
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && movieFilesError && !movieCreditsError &&
|
!isFetching && movieFilesError &&
|
||||||
<div>Loading movie files failed</div>
|
<div>Loading movie files failed</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && movieCreditsError &&
|
||||||
|
<div>Loading movie credits failed</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && extraFilesError &&
|
||||||
|
<div>Loading movie extra files failed</div>
|
||||||
|
}
|
||||||
|
|
||||||
<Tabs selectedIndex={this.state.tabIndex} onSelect={(tabIndex) => this.setState({ selectedTabIndex: tabIndex })}>
|
<Tabs selectedIndex={this.state.tabIndex} onSelect={(tabIndex) => this.setState({ selectedTabIndex: tabIndex })}>
|
||||||
<TabList
|
<TabList
|
||||||
className={styles.tabList}
|
className={styles.tabList}
|
||||||
@@ -537,6 +549,9 @@ class MovieDetails extends Component {
|
|||||||
<MovieFileEditorTable
|
<MovieFileEditorTable
|
||||||
movieId={id}
|
movieId={id}
|
||||||
/>
|
/>
|
||||||
|
<ExtraFileTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
@@ -623,6 +638,7 @@ MovieDetails.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
movieFilesError: PropTypes.object,
|
movieFilesError: PropTypes.object,
|
||||||
movieCreditsError: PropTypes.object,
|
movieCreditsError: PropTypes.object,
|
||||||
|
extraFilesError: PropTypes.object,
|
||||||
hasMovieFiles: PropTypes.bool.isRequired,
|
hasMovieFiles: PropTypes.bool.isRequired,
|
||||||
previousMovie: PropTypes.object.isRequired,
|
previousMovie: PropTypes.object.isRequired,
|
||||||
nextMovie: PropTypes.object.isRequired,
|
nextMovie: PropTypes.object.isRequired,
|
||||||
|
@@ -9,6 +9,7 @@ import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
|||||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
import { fetchMovieFiles, clearMovieFiles } from 'Store/Actions/movieFileActions';
|
import { fetchMovieFiles, clearMovieFiles } from 'Store/Actions/movieFileActions';
|
||||||
|
import { fetchExtraFiles, clearExtraFiles } from 'Store/Actions/extraFileActions';
|
||||||
import { fetchMovieCredits, clearMovieCredits } from 'Store/Actions/movieCreditsActions';
|
import { fetchMovieCredits, clearMovieCredits } from 'Store/Actions/movieCreditsActions';
|
||||||
import { toggleMovieMonitored } from 'Store/Actions/movieActions';
|
import { toggleMovieMonitored } from 'Store/Actions/movieActions';
|
||||||
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
|
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
|
||||||
@@ -59,15 +60,33 @@ const selectMovieCredits = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectExtraFiles = createSelector(
|
||||||
|
(state) => state.extraFiles,
|
||||||
|
(extraFiles) => {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error
|
||||||
|
} = extraFiles;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isExtraFilesFetching: isFetching,
|
||||||
|
isExtraFilesPopulated: isPopulated,
|
||||||
|
extraFilesError: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { titleSlug }) => titleSlug,
|
(state, { titleSlug }) => titleSlug,
|
||||||
selectMovieFiles,
|
selectMovieFiles,
|
||||||
selectMovieCredits,
|
selectMovieCredits,
|
||||||
|
selectExtraFiles,
|
||||||
createAllMoviesSelector(),
|
createAllMoviesSelector(),
|
||||||
createCommandsSelector(),
|
createCommandsSelector(),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(titleSlug, movieFiles, movieCredits, allMovies, commands, dimensions) => {
|
(titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions) => {
|
||||||
const sortedMovies = _.orderBy(allMovies, 'sortTitle');
|
const sortedMovies = _.orderBy(allMovies, 'sortTitle');
|
||||||
const movieIndex = _.findIndex(sortedMovies, { titleSlug });
|
const movieIndex = _.findIndex(sortedMovies, { titleSlug });
|
||||||
const movie = sortedMovies[movieIndex];
|
const movie = sortedMovies[movieIndex];
|
||||||
@@ -90,6 +109,12 @@ function createMapStateToProps() {
|
|||||||
movieCreditsError
|
movieCreditsError
|
||||||
} = movieCredits;
|
} = movieCredits;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isExtraFilesFetching,
|
||||||
|
isExtraFilesPopulated,
|
||||||
|
extraFilesError
|
||||||
|
} = extraFiles;
|
||||||
|
|
||||||
const previousMovie = sortedMovies[movieIndex - 1] || _.last(sortedMovies);
|
const previousMovie = sortedMovies[movieIndex - 1] || _.last(sortedMovies);
|
||||||
const nextMovie = sortedMovies[movieIndex + 1] || _.first(sortedMovies);
|
const nextMovie = sortedMovies[movieIndex + 1] || _.first(sortedMovies);
|
||||||
const isMovieRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_MOVIE, movieId: movie.id }));
|
const isMovieRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_MOVIE, movieId: movie.id }));
|
||||||
@@ -107,8 +132,8 @@ function createMapStateToProps() {
|
|||||||
isRenamingMovieCommand.body.movieIds.indexOf(movie.id) > -1
|
isRenamingMovieCommand.body.movieIds.indexOf(movie.id) > -1
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFetching = isMovieFilesFetching && isMovieCreditsFetching;
|
const isFetching = isMovieFilesFetching || isMovieCreditsFetching || isExtraFilesFetching;
|
||||||
const isPopulated = isMovieFilesPopulated && isMovieCreditsPopulated;
|
const isPopulated = isMovieFilesPopulated && isMovieCreditsPopulated && isExtraFilesPopulated;
|
||||||
const alternateTitles = _.reduce(movie.alternateTitles, (acc, alternateTitle) => {
|
const alternateTitles = _.reduce(movie.alternateTitles, (acc, alternateTitle) => {
|
||||||
acc.push(alternateTitle.title);
|
acc.push(alternateTitle.title);
|
||||||
return acc;
|
return acc;
|
||||||
@@ -127,6 +152,7 @@ function createMapStateToProps() {
|
|||||||
isPopulated,
|
isPopulated,
|
||||||
movieFilesError,
|
movieFilesError,
|
||||||
movieCreditsError,
|
movieCreditsError,
|
||||||
|
extraFilesError,
|
||||||
hasMovieFiles,
|
hasMovieFiles,
|
||||||
sizeOnDisk,
|
sizeOnDisk,
|
||||||
previousMovie,
|
previousMovie,
|
||||||
@@ -142,6 +168,8 @@ const mapDispatchToProps = {
|
|||||||
clearMovieFiles,
|
clearMovieFiles,
|
||||||
fetchMovieCredits,
|
fetchMovieCredits,
|
||||||
clearMovieCredits,
|
clearMovieCredits,
|
||||||
|
fetchExtraFiles,
|
||||||
|
clearExtraFiles,
|
||||||
clearReleases,
|
clearReleases,
|
||||||
cancelFetchReleases,
|
cancelFetchReleases,
|
||||||
fetchNetImportSchema,
|
fetchNetImportSchema,
|
||||||
@@ -200,6 +228,7 @@ class MovieDetailsConnector extends Component {
|
|||||||
const movieId = this.props.id;
|
const movieId = this.props.id;
|
||||||
|
|
||||||
this.props.fetchMovieFiles({ movieId });
|
this.props.fetchMovieFiles({ movieId });
|
||||||
|
this.props.fetchExtraFiles({ movieId });
|
||||||
this.props.fetchMovieCredits({ movieId });
|
this.props.fetchMovieCredits({ movieId });
|
||||||
this.props.fetchQueueDetails({ movieId });
|
this.props.fetchQueueDetails({ movieId });
|
||||||
this.props.fetchNetImportSchema();
|
this.props.fetchNetImportSchema();
|
||||||
@@ -208,6 +237,7 @@ class MovieDetailsConnector extends Component {
|
|||||||
unpopulate = () => {
|
unpopulate = () => {
|
||||||
this.props.cancelFetchReleases();
|
this.props.cancelFetchReleases();
|
||||||
this.props.clearMovieFiles();
|
this.props.clearMovieFiles();
|
||||||
|
this.props.clearExtraFiles();
|
||||||
this.props.clearMovieCredits();
|
this.props.clearMovieCredits();
|
||||||
this.props.clearQueueDetails();
|
this.props.clearQueueDetails();
|
||||||
this.props.clearReleases();
|
this.props.clearReleases();
|
||||||
@@ -263,6 +293,8 @@ MovieDetailsConnector.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
fetchMovieFiles: PropTypes.func.isRequired,
|
fetchMovieFiles: PropTypes.func.isRequired,
|
||||||
clearMovieFiles: PropTypes.func.isRequired,
|
clearMovieFiles: PropTypes.func.isRequired,
|
||||||
|
fetchExtraFiles: PropTypes.func.isRequired,
|
||||||
|
clearExtraFiles: PropTypes.func.isRequired,
|
||||||
fetchMovieCredits: PropTypes.func.isRequired,
|
fetchMovieCredits: PropTypes.func.isRequired,
|
||||||
clearMovieCredits: PropTypes.func.isRequired,
|
clearMovieCredits: PropTypes.func.isRequired,
|
||||||
clearReleases: PropTypes.func.isRequired,
|
clearReleases: PropTypes.func.isRequired,
|
||||||
|
10
frontend/src/MovieFile/Editor/MovieFileEditorTable.css
Normal file
10
frontend/src/MovieFile/Editor/MovieFileEditorTable.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.container {
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 1px solid $borderColor;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $white;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MovieFileEditorTableContentConnector from './MovieFileEditorTableContentConnector';
|
import MovieFileEditorTableContentConnector from './MovieFileEditorTableContentConnector';
|
||||||
|
import styles from './MovieFileEditorTable.css';
|
||||||
|
|
||||||
function MovieFileEditorTable(props) {
|
function MovieFileEditorTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -8,9 +9,11 @@ function MovieFileEditorTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
<MovieFileEditorTableContentConnector
|
<MovieFileEditorTableContentConnector
|
||||||
movieId={movieId}
|
movieId={movieId}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
frontend/src/MovieFile/Extras/ExtraFileRow.css
Normal file
10
frontend/src/MovieFile/Extras/ExtraFileRow.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.relativePath {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension,
|
||||||
|
.type {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
}
|
63
frontend/src/MovieFile/Extras/ExtraFileRow.js
Normal file
63
frontend/src/MovieFile/Extras/ExtraFileRow.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import titleCase from 'Utilities/String/titleCase';
|
||||||
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
|
import styles from './ExtraFileRow.css';
|
||||||
|
|
||||||
|
class ExtraFileRow extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
relativePath,
|
||||||
|
extension,
|
||||||
|
type
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableRowCell
|
||||||
|
className={styles.relativePath}
|
||||||
|
title={relativePath}
|
||||||
|
>
|
||||||
|
{relativePath}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell
|
||||||
|
className={styles.extension}
|
||||||
|
title={extension}
|
||||||
|
>
|
||||||
|
{extension}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell
|
||||||
|
className={styles.type}
|
||||||
|
title={type}
|
||||||
|
>
|
||||||
|
{titleCase(type)}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.actions}>
|
||||||
|
<IconButton
|
||||||
|
name={icons.INFO}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtraFileRow.propTypes = {
|
||||||
|
id: PropTypes.number.isRequired,
|
||||||
|
extension: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
relativePath: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExtraFileRow;
|
10
frontend/src/MovieFile/Extras/ExtraFileTable.css
Normal file
10
frontend/src/MovieFile/Extras/ExtraFileTable.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.container {
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 1px solid $borderColor;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $white;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
25
frontend/src/MovieFile/Extras/ExtraFileTable.js
Normal file
25
frontend/src/MovieFile/Extras/ExtraFileTable.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import ExtraFileTableContentConnector from './ExtraFileTableContentConnector';
|
||||||
|
import styles from './ExtraFileTable.css';
|
||||||
|
|
||||||
|
function ExtraFileTable(props) {
|
||||||
|
const {
|
||||||
|
movieId
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<ExtraFileTableContentConnector
|
||||||
|
movieId={movieId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtraFileTable.propTypes = {
|
||||||
|
movieId: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExtraFileTable;
|
10
frontend/src/MovieFile/Extras/ExtraFileTableContent.css
Normal file
10
frontend/src/MovieFile/Extras/ExtraFileTableContent.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blankpad {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
80
frontend/src/MovieFile/Extras/ExtraFileTableContent.js
Normal file
80
frontend/src/MovieFile/Extras/ExtraFileTableContent.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import ExtraFileRow from './ExtraFileRow';
|
||||||
|
import styles from './ExtraFileTableContent.css';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'relativePath',
|
||||||
|
label: 'Extra File',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extension',
|
||||||
|
label: 'Extension',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
label: 'Type',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'action',
|
||||||
|
label: React.createElement(IconButton, { name: icons.ADVANCED_SETTINGS }),
|
||||||
|
isVisible: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class ExtraFileTableContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
!items.length &&
|
||||||
|
<div className={styles.blankpad}>
|
||||||
|
No extra files to manage.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!!items.length &&
|
||||||
|
<Table columns={columns}>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
items.map((item) => {
|
||||||
|
return (
|
||||||
|
<ExtraFileRow
|
||||||
|
key={item.id}
|
||||||
|
{...item}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtraFileTableContent.propTypes = {
|
||||||
|
movieId: PropTypes.number,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExtraFileTableContent;
|
@@ -0,0 +1,50 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||||
|
import ExtraFileTableContent from './ExtraFileTableContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.extraFiles,
|
||||||
|
createMovieSelector(),
|
||||||
|
(
|
||||||
|
ExtraFiles
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
items: ExtraFiles.items,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExtraFileTableContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExtraFileTableContent
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtraFileTableContentConnector.propTypes = {
|
||||||
|
movieId: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, createMapDispatchToProps)(ExtraFileTableContentConnector);
|
49
frontend/src/Store/Actions/extraFileActions.js
Normal file
49
frontend/src/Store/Actions/extraFileActions.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { createAction } from 'redux-actions';
|
||||||
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
|
import createFetchHandler from './Creators/createFetchHandler';
|
||||||
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
export const section = 'extraFiles';
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
export const defaultState = {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
items: []
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_EXTRA_FILES = 'extraFiles/fetchExtraFiles';
|
||||||
|
export const CLEAR_EXTRA_FILES = 'extraFiles/clearExtraFiles';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchExtraFiles = createThunk(FETCH_EXTRA_FILES);
|
||||||
|
export const clearExtraFiles = createAction(CLEAR_EXTRA_FILES);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
export const actionHandlers = handleThunks({
|
||||||
|
[FETCH_EXTRA_FILES]: createFetchHandler(section, '/extraFile')
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
export const reducers = createHandleActions({
|
||||||
|
|
||||||
|
[CLEAR_EXTRA_FILES]: (state) => {
|
||||||
|
return Object.assign({}, state, defaultState);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, defaultState, section);
|
@@ -6,6 +6,7 @@ import * as captcha from './captchaActions';
|
|||||||
import * as customFilters from './customFilterActions';
|
import * as customFilters from './customFilterActions';
|
||||||
import * as commands from './commandActions';
|
import * as commands from './commandActions';
|
||||||
import * as movieFiles from './movieFileActions';
|
import * as movieFiles from './movieFileActions';
|
||||||
|
import * as extraFiles from './extraFileActions';
|
||||||
import * as history from './historyActions';
|
import * as history from './historyActions';
|
||||||
import * as importMovie from './importMovieActions';
|
import * as importMovie from './importMovieActions';
|
||||||
import * as interactiveImportActions from './interactiveImportActions';
|
import * as interactiveImportActions from './interactiveImportActions';
|
||||||
@@ -33,6 +34,7 @@ export default [
|
|||||||
commands,
|
commands,
|
||||||
customFilters,
|
customFilters,
|
||||||
movieFiles,
|
movieFiles,
|
||||||
|
extraFiles,
|
||||||
history,
|
history,
|
||||||
importMovie,
|
importMovie,
|
||||||
interactiveImportActions,
|
interactiveImportActions,
|
||||||
|
46
src/Radarr.Api.V3/ExtraFiles/ExtraFileModule.cs
Normal file
46
src/Radarr.Api.V3/ExtraFiles/ExtraFileModule.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
|
using NzbDrone.Core.Extras.Others;
|
||||||
|
using NzbDrone.Core.Extras.Subtitles;
|
||||||
|
using Radarr.Http;
|
||||||
|
using Radarr.Http.REST;
|
||||||
|
|
||||||
|
namespace Radarr.Api.V3.ExtraFiles
|
||||||
|
{
|
||||||
|
public class ExtraFileModule : RadarrRestModule<ExtraFileResource>
|
||||||
|
{
|
||||||
|
private readonly IExtraFileService<SubtitleFile> _subtitleFileService;
|
||||||
|
private readonly IExtraFileService<MetadataFile> _metadataFileService;
|
||||||
|
private readonly IExtraFileService<OtherExtraFile> _otherFileService;
|
||||||
|
|
||||||
|
public ExtraFileModule(IExtraFileService<SubtitleFile> subtitleFileService, IExtraFileService<MetadataFile> metadataFileService, IExtraFileService<OtherExtraFile> otherExtraFileService)
|
||||||
|
: base("/extrafile")
|
||||||
|
{
|
||||||
|
_subtitleFileService = subtitleFileService;
|
||||||
|
_metadataFileService = metadataFileService;
|
||||||
|
_otherFileService = otherExtraFileService;
|
||||||
|
GetResourceAll = GetFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ExtraFileResource> GetFiles()
|
||||||
|
{
|
||||||
|
if (!Request.Query.MovieId.HasValue)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("MovieId is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
var extraFiles = new List<ExtraFileResource>();
|
||||||
|
|
||||||
|
List<SubtitleFile> subtitleFiles = _subtitleFileService.GetFilesByMovie(Request.Query.MovieId);
|
||||||
|
List<MetadataFile> metadataFiles = _metadataFileService.GetFilesByMovie(Request.Query.MovieId);
|
||||||
|
List<OtherExtraFile> otherExtraFiles = _otherFileService.GetFilesByMovie(Request.Query.MovieId);
|
||||||
|
|
||||||
|
extraFiles.AddRange(subtitleFiles.ToResource());
|
||||||
|
extraFiles.AddRange(metadataFiles.ToResource());
|
||||||
|
extraFiles.AddRange(otherExtraFiles.ToResource());
|
||||||
|
|
||||||
|
return extraFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
src/Radarr.Api.V3/ExtraFiles/ExtraFileResource.cs
Normal file
91
src/Radarr.Api.V3/ExtraFiles/ExtraFileResource.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Extras.Files;
|
||||||
|
using NzbDrone.Core.Extras.Metadata.Files;
|
||||||
|
using NzbDrone.Core.Extras.Others;
|
||||||
|
using NzbDrone.Core.Extras.Subtitles;
|
||||||
|
using Radarr.Http.REST;
|
||||||
|
|
||||||
|
namespace Radarr.Api.V3.ExtraFiles
|
||||||
|
{
|
||||||
|
public class ExtraFileResource : RestResource
|
||||||
|
{
|
||||||
|
public int MovieId { get; set; }
|
||||||
|
public int? MovieFileId { get; set; }
|
||||||
|
public string RelativePath { get; set; }
|
||||||
|
public string Extension { get; set; }
|
||||||
|
public ExtraFileType Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExtraFileResourceMapper
|
||||||
|
{
|
||||||
|
public static ExtraFileResource ToResource(this MetadataFile model)
|
||||||
|
{
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExtraFileResource
|
||||||
|
{
|
||||||
|
Id = model.Id,
|
||||||
|
MovieId = model.MovieId,
|
||||||
|
MovieFileId = model.MovieFileId,
|
||||||
|
RelativePath = model.RelativePath,
|
||||||
|
Extension = model.Extension,
|
||||||
|
Type = ExtraFileType.Metadata
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExtraFileResource ToResource(this SubtitleFile model)
|
||||||
|
{
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExtraFileResource
|
||||||
|
{
|
||||||
|
Id = model.Id,
|
||||||
|
MovieId = model.MovieId,
|
||||||
|
MovieFileId = model.MovieFileId,
|
||||||
|
RelativePath = model.RelativePath,
|
||||||
|
Extension = model.Extension,
|
||||||
|
Type = ExtraFileType.Subtitle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExtraFileResource ToResource(this OtherExtraFile model)
|
||||||
|
{
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExtraFileResource
|
||||||
|
{
|
||||||
|
Id = model.Id,
|
||||||
|
MovieId = model.MovieId,
|
||||||
|
MovieFileId = model.MovieFileId,
|
||||||
|
RelativePath = model.RelativePath,
|
||||||
|
Extension = model.Extension,
|
||||||
|
Type = ExtraFileType.Other
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ExtraFileResource> ToResource(this IEnumerable<SubtitleFile> movies)
|
||||||
|
{
|
||||||
|
return movies.Select(ToResource).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ExtraFileResource> ToResource(this IEnumerable<MetadataFile> movies)
|
||||||
|
{
|
||||||
|
return movies.Select(ToResource).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ExtraFileResource> ToResource(this IEnumerable<OtherExtraFile> movies)
|
||||||
|
{
|
||||||
|
return movies.Select(ToResource).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user