parent
3576f529ec
commit
f338941cfc
@ -0,0 +1,10 @@
|
|||||||
|
.container {
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 1px solid $borderColor;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $white;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -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';
|
||||||
|
}
|
@ -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;
|
@ -0,0 +1,10 @@
|
|||||||
|
.container {
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 1px solid $borderColor;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $white;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
@ -0,0 +1,10 @@
|
|||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blankpad {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
@ -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);
|
@ -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);
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue