mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
Fixed: Movie Details Tab (#3564)
* History Added * History Cleanup * History Mark Failed Fix * History Lint Fix * Search Tab Initial * Interactive Search Cleanup * Files Tab + Small Backend change to MovieFile api * Reverse Movie History Items * Grabbed files are not grabbable again. * Partial movie title outline + Search not updating fix * Lint Fix + InteractiveSearch refactor * Rename movieLanguage.js to MovieLanguage.js * Fixes for qstick's comments * Rename language selector to allow for const languages * Qstick comment changes. * Activity Tabs - Language Column fixed * Movie Details - MoveStatusLabel fixed * Spaces + Lower Case added * fixed DownloadAllowed * Added padding to history and file tables * Fix class => className * Updated search to not refresh unless switching movie * lint fix * File Tab Converted to Inline Editting * FIles tab fix + Alt Titles tab implemented * lint fix * Cleanup via qstick request
This commit is contained in:
@@ -6,6 +6,7 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo
|
|||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
import BlacklistDetailsModal from './BlacklistDetailsModal';
|
import BlacklistDetailsModal from './BlacklistDetailsModal';
|
||||||
import styles from './BlacklistRow.css';
|
import styles from './BlacklistRow.css';
|
||||||
@@ -42,6 +43,7 @@ class BlacklistRow extends Component {
|
|||||||
movie,
|
movie,
|
||||||
sourceTitle,
|
sourceTitle,
|
||||||
quality,
|
quality,
|
||||||
|
languages,
|
||||||
date,
|
date,
|
||||||
protocol,
|
protocol,
|
||||||
indexer,
|
indexer,
|
||||||
@@ -82,6 +84,16 @@ class BlacklistRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'language') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
<MovieLanguage
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'quality') {
|
if (name === 'quality') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
@@ -159,6 +171,7 @@ BlacklistRow.propTypes = {
|
|||||||
movie: PropTypes.object.isRequired,
|
movie: PropTypes.object.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
indexer: PropTypes.string,
|
indexer: PropTypes.string,
|
||||||
|
@@ -6,6 +6,7 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo
|
|||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||||
@@ -52,6 +53,7 @@ class HistoryRow extends Component {
|
|||||||
const {
|
const {
|
||||||
movie,
|
movie,
|
||||||
quality,
|
quality,
|
||||||
|
languages,
|
||||||
qualityCutoffNotMet,
|
qualityCutoffNotMet,
|
||||||
eventType,
|
eventType,
|
||||||
sourceTitle,
|
sourceTitle,
|
||||||
@@ -102,6 +104,16 @@ class HistoryRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'language') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
<MovieLanguage
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'quality') {
|
if (name === 'quality') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
@@ -193,8 +205,7 @@ class HistoryRow extends Component {
|
|||||||
HistoryRow.propTypes = {
|
HistoryRow.propTypes = {
|
||||||
movieId: PropTypes.number,
|
movieId: PropTypes.number,
|
||||||
movie: PropTypes.object.isRequired,
|
movie: PropTypes.object.isRequired,
|
||||||
language: PropTypes.object.isRequired,
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
languageCutoffNotMet: PropTypes.bool.isRequired,
|
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
eventType: PropTypes.string.isRequired,
|
eventType: PropTypes.string.isRequired,
|
||||||
|
@@ -10,6 +10,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
import QueueStatusCell from './QueueStatusCell';
|
import QueueStatusCell from './QueueStatusCell';
|
||||||
@@ -69,6 +70,7 @@ class QueueRow extends Component {
|
|||||||
errorMessage,
|
errorMessage,
|
||||||
movie,
|
movie,
|
||||||
quality,
|
quality,
|
||||||
|
languages,
|
||||||
protocol,
|
protocol,
|
||||||
indexer,
|
indexer,
|
||||||
outputPath,
|
outputPath,
|
||||||
@@ -145,6 +147,16 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'language') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
<MovieLanguage
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'quality') {
|
if (name === 'quality') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
@@ -297,6 +309,7 @@ QueueRow.propTypes = {
|
|||||||
errorMessage: PropTypes.string,
|
errorMessage: PropTypes.string,
|
||||||
movie: PropTypes.object.isRequired,
|
movie: PropTypes.object.isRequired,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
indexer: PropTypes.string,
|
indexer: PropTypes.string,
|
||||||
outputPath: PropTypes.string,
|
outputPath: PropTypes.string,
|
||||||
|
@@ -128,7 +128,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||||||
folderName: item.folderName,
|
folderName: item.folderName,
|
||||||
movieId: movie.id,
|
movieId: movie.id,
|
||||||
quality,
|
quality,
|
||||||
language,
|
languages: [language],
|
||||||
downloadId: this.props.downloadId
|
downloadId: this.props.downloadId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -7,8 +7,6 @@
|
|||||||
.quality,
|
.quality,
|
||||||
.language {
|
.language {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
@@ -9,7 +9,7 @@ import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton';
|
|||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
// import MovieLanguage from 'Movie/MovieLanguage';
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
|
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
|
||||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||||
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
||||||
@@ -152,7 +152,8 @@ class InteractiveImportRow extends Component {
|
|||||||
const showMoviePlaceholder = isSelected && !movie;
|
const showMoviePlaceholder = isSelected && !movie;
|
||||||
const showQualityPlaceholder = isSelected && !quality;
|
const showQualityPlaceholder = isSelected && !quality;
|
||||||
const showLanguagePlaceholder = isSelected && !language;
|
const showLanguagePlaceholder = isSelected && !language;
|
||||||
|
// TODO - Placeholder till we implement selection of multiple languages
|
||||||
|
const languages = [language];
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableSelectCell
|
<TableSelectCell
|
||||||
@@ -207,13 +208,13 @@ class InteractiveImportRow extends Component {
|
|||||||
<InteractiveImportRowCellPlaceholder />
|
<InteractiveImportRowCellPlaceholder />
|
||||||
}
|
}
|
||||||
|
|
||||||
{/* {
|
{
|
||||||
!showLanguagePlaceholder && !!language &&
|
!showLanguagePlaceholder && !!language &&
|
||||||
<MovieLanguage
|
<MovieLanguage
|
||||||
className={styles.label}
|
className={styles.label}
|
||||||
language={language}
|
languages={languages}
|
||||||
/>
|
/>
|
||||||
} */}
|
}
|
||||||
</TableRowCellButton>
|
</TableRowCellButton>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
|
@@ -9,7 +9,7 @@ import Table from 'Components/Table/Table';
|
|||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
||||||
import InteractiveSearchRow from './InteractiveSearchRow';
|
import InteractiveSearchRow from './InteractiveSearchRow';
|
||||||
import styles from './InteractiveSearch.css';
|
import styles from './InteractiveSearchContent.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -48,6 +48,12 @@ const columns = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'languageWeight',
|
||||||
|
label: 'Language',
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'qualityWeight',
|
name: 'qualityWeight',
|
||||||
label: 'Quality',
|
label: 'Quality',
|
||||||
@@ -70,7 +76,7 @@ const columns = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function InteractiveSearch(props) {
|
function InteractiveSearchContent(props) {
|
||||||
const {
|
const {
|
||||||
searchPayload,
|
searchPayload,
|
||||||
isFetching,
|
isFetching,
|
||||||
@@ -83,7 +89,6 @@ function InteractiveSearch(props) {
|
|||||||
customFilters,
|
customFilters,
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
type,
|
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
@@ -101,7 +106,6 @@ function InteractiveSearch(props) {
|
|||||||
customFilters={customFilters}
|
customFilters={customFilters}
|
||||||
buttonComponent={PageMenuButton}
|
buttonComponent={PageMenuButton}
|
||||||
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
|
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
|
||||||
filterModalConnectorComponentProps={{ type }}
|
|
||||||
onFilterSelect={onFilterSelect}
|
onFilterSelect={onFilterSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,7 +173,7 @@ function InteractiveSearch(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveSearch.propTypes = {
|
InteractiveSearchContent.propTypes = {
|
||||||
searchPayload: PropTypes.object.isRequired,
|
searchPayload: PropTypes.object.isRequired,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
@@ -181,7 +185,6 @@ InteractiveSearch.propTypes = {
|
|||||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.string,
|
sortDirection: PropTypes.string,
|
||||||
type: PropTypes.string.isRequired,
|
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
@@ -189,4 +192,4 @@ InteractiveSearch.propTypes = {
|
|||||||
onGrabPress: PropTypes.func.isRequired
|
onGrabPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InteractiveSearch;
|
export default InteractiveSearchContent;
|
@@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
|
|||||||
import * as releaseActions from 'Store/Actions/releaseActions';
|
import * as releaseActions from 'Store/Actions/releaseActions';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import InteractiveSearch from './InteractiveSearch';
|
import InteractiveSearchContent from './InteractiveSearchContent';
|
||||||
|
|
||||||
function createMapStateToProps(appState, { type }) {
|
function createMapStateToProps(appState) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.releases.items.length,
|
(state) => state.releases.items.length,
|
||||||
createClientSideCollectionSelector('releases', `releases.${type}`),
|
createClientSideCollectionSelector('releases'),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(totalReleasesCount, releases, uiSettings) => {
|
(totalReleasesCount, releases, uiSettings) => {
|
||||||
return {
|
return {
|
||||||
@@ -29,15 +29,16 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(releaseActions.fetchReleases(payload));
|
dispatch(releaseActions.fetchReleases(payload));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dispatchClearReleases(payload) {
|
||||||
|
dispatch(releaseActions.clearReleases(payload));
|
||||||
|
},
|
||||||
|
|
||||||
onSortPress(sortKey, sortDirection) {
|
onSortPress(sortKey, sortDirection) {
|
||||||
dispatch(releaseActions.setReleasesSort({ sortKey, sortDirection }));
|
dispatch(releaseActions.setReleasesSort({ sortKey, sortDirection }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onFilterSelect(selectedFilterKey) {
|
onFilterSelect(selectedFilterKey) {
|
||||||
const action = props.type === 'episode' ?
|
const action = releaseActions.setReleasesFilter;
|
||||||
releaseActions.setEpisodeReleasesFilter :
|
|
||||||
releaseActions.setSeasonReleasesFilter;
|
|
||||||
|
|
||||||
dispatch(action({ selectedFilterKey }));
|
dispatch(action({ selectedFilterKey }));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class InteractiveSearchConnector extends Component {
|
class InteractiveSearchContentConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@@ -61,7 +62,6 @@ class InteractiveSearchConnector extends Component {
|
|||||||
|
|
||||||
// If search results are not yet isPopulated fetch them,
|
// If search results are not yet isPopulated fetch them,
|
||||||
// otherwise re-show the existing props.
|
// otherwise re-show the existing props.
|
||||||
|
|
||||||
if (!isPopulated) {
|
if (!isPopulated) {
|
||||||
dispatchFetchReleases(searchPayload);
|
dispatchFetchReleases(searchPayload);
|
||||||
}
|
}
|
||||||
@@ -73,22 +73,24 @@ class InteractiveSearchConnector extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
dispatchFetchReleases,
|
dispatchFetchReleases,
|
||||||
|
dispatchClearReleases,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<InteractiveSearch
|
<InteractiveSearchContent
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveSearchConnector.propTypes = {
|
InteractiveSearchContentConnector.propTypes = {
|
||||||
searchPayload: PropTypes.object.isRequired,
|
searchPayload: PropTypes.object.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
dispatchFetchReleases: PropTypes.func.isRequired
|
dispatchFetchReleases: PropTypes.func.isRequired,
|
||||||
|
dispatchClearReleases: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchConnector);
|
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchContentConnector);
|
@@ -1,6 +1,6 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { setEpisodeReleasesFilter, setSeasonReleasesFilter } from 'Store/Actions/releaseActions';
|
import { setReleasesFilter } from 'Store/Actions/releaseActions';
|
||||||
import FilterModal from 'Components/Filter/FilterModal';
|
import FilterModal from 'Components/Filter/FilterModal';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
@@ -20,10 +20,7 @@ function createMapStateToProps() {
|
|||||||
function createMapDispatchToProps(dispatch, props) {
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
return {
|
return {
|
||||||
dispatchSetFilter(payload) {
|
dispatchSetFilter(payload) {
|
||||||
const action = props.type === 'episode' ?
|
const action = setReleasesFilter;
|
||||||
setEpisodeReleasesFilter:
|
|
||||||
setSeasonReleasesFilter;
|
|
||||||
|
|
||||||
dispatch(action(payload));
|
dispatch(action(payload));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
.title {
|
.quality,
|
||||||
|
.language {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.quality {
|
.language {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
width: 100px;
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rejected,
|
.rejected,
|
||||||
|
@@ -11,10 +11,11 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
|
||||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||||
import Peers from './Peers';
|
import Peers from './Peers';
|
||||||
import styles from './InteractiveSearchRow.css';
|
import styles from './InteractiveSearchRow.css';
|
||||||
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
|
|
||||||
function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
|
function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
|
||||||
if (isGrabbing) {
|
if (isGrabbing) {
|
||||||
@@ -111,6 +112,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
seeders,
|
seeders,
|
||||||
leechers,
|
leechers,
|
||||||
quality,
|
quality,
|
||||||
|
languages,
|
||||||
rejections,
|
rejections,
|
||||||
downloadAllowed,
|
downloadAllowed,
|
||||||
isGrabbing,
|
isGrabbing,
|
||||||
@@ -159,8 +161,14 @@ class InteractiveSearchRow extends Component {
|
|||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.language}>
|
||||||
|
<MovieLanguage
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.quality}>
|
<TableRowCell className={styles.quality}>
|
||||||
<EpisodeQuality
|
<MovieQuality
|
||||||
quality={quality}
|
quality={quality}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
@@ -199,6 +207,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||||
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
||||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||||
|
isDisabled={isGrabbed}
|
||||||
isSpinning={isGrabbing}
|
isSpinning={isGrabbing}
|
||||||
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
||||||
/>
|
/>
|
||||||
@@ -208,7 +217,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
isOpen={this.state.isConfirmGrabModalOpen}
|
isOpen={this.state.isConfirmGrabModalOpen}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
title="Grab Release"
|
title="Grab Release"
|
||||||
message={`Sonarr was unable to determine which series and episode this release was for. Sonarr may be unable to automatically import this release. Do you want to grab '${title}'?`}
|
message={`Radarr was unable to determine which movie this release was for. Radarr may be unable to automatically import this release. Do you want to grab '${title}'?`}
|
||||||
confirmLabel="Grab"
|
confirmLabel="Grab"
|
||||||
onConfirm={this.onGrabConfirm}
|
onConfirm={this.onGrabConfirm}
|
||||||
onCancel={this.onGrabCancel}
|
onCancel={this.onGrabCancel}
|
||||||
@@ -233,6 +242,7 @@ InteractiveSearchRow.propTypes = {
|
|||||||
seeders: PropTypes.number,
|
seeders: PropTypes.number,
|
||||||
leechers: PropTypes.number,
|
leechers: PropTypes.number,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
rejections: PropTypes.arrayOf(PropTypes.string).isRequired,
|
rejections: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
downloadAllowed: PropTypes.bool.isRequired,
|
downloadAllowed: PropTypes.bool.isRequired,
|
||||||
isGrabbing: PropTypes.bool.isRequired,
|
isGrabbing: PropTypes.bool.isRequired,
|
||||||
|
16
frontend/src/InteractiveSearch/InteractiveSearchTable.js
Normal file
16
frontend/src/InteractiveSearch/InteractiveSearchTable.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import InteractiveSearchContentConnector from './InteractiveSearchContentConnector';
|
||||||
|
|
||||||
|
function InteractiveSearchTable(props) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InteractiveSearchContentConnector
|
||||||
|
searchPayload={props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractiveSearchTable.propTypes = {
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InteractiveSearchTable;
|
@@ -22,15 +22,17 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import MovieFileEditorModal from 'MovieFile/Editor/MovieFileEditorModal';
|
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
||||||
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';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||||
import MovieHistoryModal from 'Movie/History/MovieHistoryModal';
|
import MovieHistoryTable from 'Movie/History/MovieHistoryTable';
|
||||||
|
import MovieTitlesTable from 'Movie/Titles/MovieTitlesTable';
|
||||||
import MovieAlternateTitles from './MovieAlternateTitles';
|
import MovieAlternateTitles from './MovieAlternateTitles';
|
||||||
import MovieDetailsLinks from './MovieDetailsLinks';
|
import MovieDetailsLinks from './MovieDetailsLinks';
|
||||||
|
import InteractiveSearchTable from '../../InteractiveSearch/InteractiveSearchTable';
|
||||||
// import MovieTagsConnector from './MovieTagsConnector';
|
// import MovieTagsConnector from './MovieTagsConnector';
|
||||||
import styles from './MovieDetails.css';
|
import styles from './MovieDetails.css';
|
||||||
import InteractiveImportModal from '../../InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from '../../InteractiveImport/InteractiveImportModal';
|
||||||
@@ -68,7 +70,6 @@ class MovieDetails extends Component {
|
|||||||
isManageEpisodesOpen: false,
|
isManageEpisodesOpen: false,
|
||||||
isEditMovieModalOpen: false,
|
isEditMovieModalOpen: false,
|
||||||
isDeleteMovieModalOpen: false,
|
isDeleteMovieModalOpen: false,
|
||||||
isMovieHistoryModalOpen: false,
|
|
||||||
isInteractiveImportModalOpen: false,
|
isInteractiveImportModalOpen: false,
|
||||||
allExpanded: false,
|
allExpanded: false,
|
||||||
allCollapsed: false,
|
allCollapsed: false,
|
||||||
@@ -123,14 +124,6 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ isDeleteMovieModalOpen: false });
|
this.setState({ isDeleteMovieModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onMovieHistoryPress = () => {
|
|
||||||
this.setState({ isMovieHistoryModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onMovieHistoryModalClose = () => {
|
|
||||||
this.setState({ isMovieHistoryModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onExpandAllPress = () => {
|
onExpandAllPress = () => {
|
||||||
const {
|
const {
|
||||||
allExpanded,
|
allExpanded,
|
||||||
@@ -195,10 +188,8 @@ class MovieDetails extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isManageEpisodesOpen,
|
|
||||||
isEditMovieModalOpen,
|
isEditMovieModalOpen,
|
||||||
isDeleteMovieModalOpen,
|
isDeleteMovieModalOpen,
|
||||||
isMovieHistoryModalOpen,
|
|
||||||
isInteractiveImportModalOpen,
|
isInteractiveImportModalOpen,
|
||||||
overviewHeight
|
overviewHeight
|
||||||
} = this.state;
|
} = this.state;
|
||||||
@@ -488,19 +479,27 @@ class MovieDetails extends Component {
|
|||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<h2>Any content 1</h2>
|
<MovieHistoryTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<h2>Any content 2</h2>
|
<InteractiveSearchTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<h2>Any content 3</h2>
|
<MovieFileEditorTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<h2>Any content 4</h2>
|
<MovieTitlesTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
@@ -512,18 +511,6 @@ class MovieDetails extends Component {
|
|||||||
onModalClose={this.onOrganizeModalClose}
|
onModalClose={this.onOrganizeModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MovieFileEditorModal
|
|
||||||
isOpen={isManageEpisodesOpen}
|
|
||||||
movieId={id}
|
|
||||||
onModalClose={this.onManageEpisodesModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MovieHistoryModal
|
|
||||||
isOpen={isMovieHistoryModalOpen}
|
|
||||||
movieId={id}
|
|
||||||
onModalClose={this.onMovieHistoryModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EditMovieModalConnector
|
<EditMovieModalConnector
|
||||||
isOpen={isEditMovieModalOpen}
|
isOpen={isEditMovieModalOpen}
|
||||||
movieId={id}
|
movieId={id}
|
||||||
|
@@ -10,6 +10,7 @@ import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
|||||||
import { fetchMovieFiles, clearMovieFiles } from 'Store/Actions/movieFileActions';
|
import { fetchMovieFiles, clearMovieFiles } from 'Store/Actions/movieFileActions';
|
||||||
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';
|
||||||
|
import { clearReleases } from 'Store/Actions/releaseActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import MovieDetails from './MovieDetails';
|
import MovieDetails from './MovieDetails';
|
||||||
@@ -108,6 +109,7 @@ function createMapStateToProps() {
|
|||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchMovieFiles,
|
fetchMovieFiles,
|
||||||
clearMovieFiles,
|
clearMovieFiles,
|
||||||
|
clearReleases,
|
||||||
toggleMovieMonitored,
|
toggleMovieMonitored,
|
||||||
fetchQueueDetails,
|
fetchQueueDetails,
|
||||||
clearQueueDetails,
|
clearQueueDetails,
|
||||||
@@ -169,6 +171,7 @@ class MovieDetailsConnector extends Component {
|
|||||||
unpopulate = () => {
|
unpopulate = () => {
|
||||||
this.props.clearMovieFiles();
|
this.props.clearMovieFiles();
|
||||||
this.props.clearQueueDetails();
|
this.props.clearQueueDetails();
|
||||||
|
this.props.clearReleases();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -220,6 +223,7 @@ MovieDetailsConnector.propTypes = {
|
|||||||
isRenamingMovie: PropTypes.bool.isRequired,
|
isRenamingMovie: PropTypes.bool.isRequired,
|
||||||
fetchMovieFiles: PropTypes.func.isRequired,
|
fetchMovieFiles: PropTypes.func.isRequired,
|
||||||
clearMovieFiles: PropTypes.func.isRequired,
|
clearMovieFiles: PropTypes.func.isRequired,
|
||||||
|
clearReleases: PropTypes.func.isRequired,
|
||||||
toggleMovieMonitored: PropTypes.func.isRequired,
|
toggleMovieMonitored: PropTypes.func.isRequired,
|
||||||
fetchQueueDetails: PropTypes.func.isRequired,
|
fetchQueueDetails: PropTypes.func.isRequired,
|
||||||
clearQueueDetails: PropTypes.func.isRequired,
|
clearQueueDetails: PropTypes.func.isRequired,
|
||||||
|
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
.downloaded {
|
.downloaded {
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
border-left: 4px solid $dangerColor;
|
border-left: 4px solid $successColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unaired {
|
.unreleased {
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
border-left: 4px solid $gray;
|
border-left: 4px solid $primaryColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
|
@@ -18,7 +18,7 @@ function getMovieStatus(hasFile, isMonitored, inCinemas) {
|
|||||||
return 'Missing';
|
return 'Missing';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Unaired';
|
return 'Unreleased';
|
||||||
}
|
}
|
||||||
|
|
||||||
function MovieStatusLabel(props) {
|
function MovieStatusLabel(props) {
|
||||||
|
@@ -1,31 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import MovieHistoryModalContentConnector from './MovieHistoryModalContentConnector';
|
|
||||||
|
|
||||||
function MovieHistoryModal(props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
onModalClose,
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<MovieHistoryModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieHistoryModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieHistoryModal;
|
|
@@ -1,136 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import MovieHistoryRowConnector from './MovieHistoryRowConnector';
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
name: 'eventType',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'episode',
|
|
||||||
label: 'Episode',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sourceTitle',
|
|
||||||
label: 'Source Title',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'language',
|
|
||||||
label: 'Language',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'quality',
|
|
||||||
label: 'Quality',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'date',
|
|
||||||
label: 'Date',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'details',
|
|
||||||
label: 'Details',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'actions',
|
|
||||||
label: 'Actions',
|
|
||||||
isVisible: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
class MovieHistoryModalContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
seasonNumber,
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
items,
|
|
||||||
onMarkAsFailedPress,
|
|
||||||
onModalClose
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const fullSeries = seasonNumber == null;
|
|
||||||
const hasItems = !!items.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
History
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
{
|
|
||||||
isFetching &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && !!error &&
|
|
||||||
<div>Unable to load history.</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated && !hasItems && !error &&
|
|
||||||
<div>No history.</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated && hasItems && !error &&
|
|
||||||
<Table columns={columns}>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<MovieHistoryRowConnector
|
|
||||||
key={item.id}
|
|
||||||
fullSeries={fullSeries}
|
|
||||||
{...item}
|
|
||||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
}
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={onModalClose}>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieHistoryModalContent.propTypes = {
|
|
||||||
seasonNumber: PropTypes.number,
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onMarkAsFailedPress: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieHistoryModalContent;
|
|
@@ -9,6 +9,7 @@ import TableRow from 'Components/Table/TableRow';
|
|||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
|
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
|
||||||
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
|
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
|
||||||
import styles from './MovieHistoryRow.css';
|
import styles from './MovieHistoryRow.css';
|
||||||
@@ -20,7 +21,8 @@ function getTitle(eventType) {
|
|||||||
case 'downloadFolderImported': return 'Download Folder Imported';
|
case 'downloadFolderImported': return 'Download Folder Imported';
|
||||||
case 'downloadFailed': return 'Download Failed';
|
case 'downloadFailed': return 'Download Failed';
|
||||||
case 'episodeFileDeleted': return 'Episode File Deleted';
|
case 'episodeFileDeleted': return 'Episode File Deleted';
|
||||||
case 'episodeFileRenamed': return 'Episode File Renamed';
|
case 'movieFileDeleted': return 'Movie File Deleted';
|
||||||
|
case 'movieFolderImported': return 'Movie Folder Imported';
|
||||||
default: return 'Unknown';
|
default: return 'Unknown';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,10 +64,10 @@ class MovieHistoryRow extends Component {
|
|||||||
eventType,
|
eventType,
|
||||||
sourceTitle,
|
sourceTitle,
|
||||||
quality,
|
quality,
|
||||||
|
languages,
|
||||||
qualityCutoffNotMet,
|
qualityCutoffNotMet,
|
||||||
date,
|
date,
|
||||||
data
|
data
|
||||||
// movie,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -83,6 +85,12 @@ class MovieHistoryRow extends Component {
|
|||||||
{sourceTitle}
|
{sourceTitle}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
<MovieLanguage
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
<MovieQuality
|
<MovieQuality
|
||||||
quality={quality}
|
quality={quality}
|
||||||
@@ -142,13 +150,11 @@ MovieHistoryRow.propTypes = {
|
|||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
eventType: PropTypes.string.isRequired,
|
eventType: PropTypes.string.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
language: PropTypes.object.isRequired,
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
languageCutoffNotMet: PropTypes.bool.isRequired,
|
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
data: PropTypes.object.isRequired,
|
data: PropTypes.object.isRequired,
|
||||||
fullSeries: PropTypes.bool.isRequired,
|
|
||||||
movie: PropTypes.object.isRequired,
|
movie: PropTypes.object.isRequired,
|
||||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
19
frontend/src/Movie/History/MovieHistoryTable.js
Normal file
19
frontend/src/Movie/History/MovieHistoryTable.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MovieHistoryTableContentConnector from './MovieHistoryTableContentConnector';
|
||||||
|
|
||||||
|
function MovieHistoryTable(props) {
|
||||||
|
const {
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MovieHistoryTableContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieHistoryTable.propTypes = {
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieHistoryTable;
|
5
frontend/src/Movie/History/MovieHistoryTableContent.css
Normal file
5
frontend/src/Movie/History/MovieHistoryTableContent.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.blankpad {
|
||||||
|
padding-left:2em;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
110
frontend/src/Movie/History/MovieHistoryTableContent.js
Normal file
110
frontend/src/Movie/History/MovieHistoryTableContent.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import MovieHistoryRowConnector from './MovieHistoryRowConnector';
|
||||||
|
import styles from './MovieHistoryTableContent.css';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'eventType',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sourceTitle',
|
||||||
|
label: 'Source Title',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'languages',
|
||||||
|
label: 'Languages',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quality',
|
||||||
|
label: 'Quality',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
label: 'Date',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'details',
|
||||||
|
label: 'Details',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
label: 'Actions',
|
||||||
|
isVisible: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class MovieHistoryTableContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items,
|
||||||
|
onMarkAsFailedPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const hasItems = !!items.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<div className={styles.blankpad}>Unable to load history</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !hasItems && !error &&
|
||||||
|
<div className={styles.blankpad}>No history</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && hasItems && !error &&
|
||||||
|
<Table columns={columns}>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
items.reverse().map((item) => {
|
||||||
|
return (
|
||||||
|
<MovieHistoryRowConnector
|
||||||
|
key={item.id}
|
||||||
|
{...item}
|
||||||
|
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieHistoryTableContent.propTypes = {
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieHistoryTableContent;
|
@@ -2,14 +2,14 @@ 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 { fetchMovieHistory, clearMovieHistory, seriesHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
import { fetchMovieHistory, clearMovieHistory, movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
||||||
import MovieHistoryModalContent from './MovieHistoryModalContent';
|
import MovieHistoryTableContent from './MovieHistoryTableContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.moviesHistory,
|
(state) => state.movieHistory,
|
||||||
(seriesHistory) => {
|
(movieHistory) => {
|
||||||
return seriesHistory;
|
return movieHistory;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -17,23 +17,21 @@ function createMapStateToProps() {
|
|||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchMovieHistory,
|
fetchMovieHistory,
|
||||||
clearMovieHistory,
|
clearMovieHistory,
|
||||||
seriesHistoryMarkAsFailed
|
movieHistoryMarkAsFailed
|
||||||
};
|
};
|
||||||
|
|
||||||
class MovieHistoryModalContentConnector extends Component {
|
class MovieHistoryTableContentConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const {
|
||||||
seriesId,
|
movieId
|
||||||
seasonNumber
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
this.props.fetchMovieHistory({
|
this.props.fetchMovieHistory({
|
||||||
seriesId,
|
movieId
|
||||||
seasonNumber
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,14 +44,12 @@ class MovieHistoryModalContentConnector extends Component {
|
|||||||
|
|
||||||
onMarkAsFailedPress = (historyId) => {
|
onMarkAsFailedPress = (historyId) => {
|
||||||
const {
|
const {
|
||||||
seriesId,
|
movieId
|
||||||
seasonNumber
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
this.props.seriesHistoryMarkAsFailed({
|
this.props.movieHistoryMarkAsFailed({
|
||||||
historyId,
|
historyId,
|
||||||
seriesId,
|
movieId
|
||||||
seasonNumber
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +58,7 @@ class MovieHistoryModalContentConnector extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<MovieHistoryModalContent
|
<MovieHistoryTableContent
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||||
/>
|
/>
|
||||||
@@ -70,12 +66,11 @@ class MovieHistoryModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieHistoryModalContentConnector.propTypes = {
|
MovieHistoryTableContentConnector.propTypes = {
|
||||||
seriesId: PropTypes.number.isRequired,
|
movieId: PropTypes.number.isRequired,
|
||||||
seasonNumber: PropTypes.number,
|
|
||||||
fetchMovieHistory: PropTypes.func.isRequired,
|
fetchMovieHistory: PropTypes.func.isRequired,
|
||||||
clearMovieHistory: PropTypes.func.isRequired,
|
clearMovieHistory: PropTypes.func.isRequired,
|
||||||
seriesHistoryMarkAsFailed: PropTypes.func.isRequired
|
movieHistoryMarkAsFailed: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryModalContentConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryTableContentConnector);
|
69
frontend/src/Movie/MovieLanguage.js
Normal file
69
frontend/src/Movie/MovieLanguage.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import { kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
|
|
||||||
|
function MovieLanguage(props) {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
languages,
|
||||||
|
isCutoffNotMet
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (!languages) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languages.length === 1) {
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
className={className}
|
||||||
|
kind={isCutoffNotMet ? kinds.INVERSE : kinds.DEFAULT}
|
||||||
|
>
|
||||||
|
{languages[0].name}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
className={className}
|
||||||
|
anchor={
|
||||||
|
<Label
|
||||||
|
className={className}
|
||||||
|
kind={isCutoffNotMet ? kinds.INVERSE : kinds.DEFAULT}
|
||||||
|
>
|
||||||
|
Multi-Language
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
title="Languages"
|
||||||
|
body={
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
languages.map((language) => {
|
||||||
|
return (
|
||||||
|
<li key={language.id}>
|
||||||
|
{language.name}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieLanguage.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
languages: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
isCutoffNotMet: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
MovieLanguage.defaultProps = {
|
||||||
|
isCutoffNotMet: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieLanguage;
|
@@ -1,36 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import SeasonInteractiveSearchModalContent from './SeasonInteractiveSearchModalContent';
|
|
||||||
|
|
||||||
function SeasonInteractiveSearchModal(props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
seriesId,
|
|
||||||
seasonNumber,
|
|
||||||
onModalClose
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
closeOnBackgroundClick={false}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<SeasonInteractiveSearchModalContent
|
|
||||||
seriesId={seriesId}
|
|
||||||
seasonNumber={seasonNumber}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SeasonInteractiveSearchModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
seriesId: PropTypes.number.isRequired,
|
|
||||||
seasonNumber: PropTypes.number.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SeasonInteractiveSearchModal;
|
|
@@ -1,15 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
|
||||||
import SeasonInteractiveSearchModal from './SeasonInteractiveSearchModal';
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
|
||||||
return {
|
|
||||||
onModalClose() {
|
|
||||||
dispatch(cancelFetchReleases());
|
|
||||||
dispatch(clearReleases());
|
|
||||||
props.onModalClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(null, createMapDispatchToProps)(SeasonInteractiveSearchModal);
|
|
@@ -1,48 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
|
||||||
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
|
|
||||||
|
|
||||||
function SeasonInteractiveSearchModalContent(props) {
|
|
||||||
const {
|
|
||||||
seriesId,
|
|
||||||
seasonNumber,
|
|
||||||
onModalClose
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
Interactive Search
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<InteractiveSearchConnector
|
|
||||||
type="season"
|
|
||||||
searchPayload={{
|
|
||||||
seriesId,
|
|
||||||
seasonNumber
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={onModalClose}>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SeasonInteractiveSearchModalContent.propTypes = {
|
|
||||||
seriesId: PropTypes.number.isRequired,
|
|
||||||
seasonNumber: PropTypes.number.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SeasonInteractiveSearchModalContent;
|
|
45
frontend/src/Movie/Titles/MovieTitlesRow.js
Normal file
45
frontend/src/Movie/Titles/MovieTitlesRow.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
|
|
||||||
|
class MovieTitlesRow extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
language
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
// TODO - Fix languages to all take arrays
|
||||||
|
const languages = [language];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
{title}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
<MovieLanguage
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieTitlesRow.propTypes = {
|
||||||
|
id: PropTypes.number.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
language: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieTitlesRow;
|
19
frontend/src/Movie/Titles/MovieTitlesTable.js
Normal file
19
frontend/src/Movie/Titles/MovieTitlesTable.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
|
||||||
|
|
||||||
|
function MovieTitlesTable(props) {
|
||||||
|
const {
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MovieTitlesTableContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieTitlesTable.propTypes = {
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieTitlesTable;
|
5
frontend/src/Movie/Titles/MovieTitlesTableContent.css
Normal file
5
frontend/src/Movie/Titles/MovieTitlesTableContent.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.blankpad {
|
||||||
|
padding-left:2em;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
81
frontend/src/Movie/Titles/MovieTitlesTableContent.js
Normal file
81
frontend/src/Movie/Titles/MovieTitlesTableContent.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import styles from './MovieTitlesTableContent.css';
|
||||||
|
import MovieTitlesRow from './MovieTitlesRow';
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'altTitle',
|
||||||
|
label: 'Alternative Title',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'language',
|
||||||
|
label: 'Language',
|
||||||
|
isVisible: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class MovieTitlesTableContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const hasItems = !!items.length;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<div className={styles.blankpad}>Unable to load alternative titles.</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !hasItems && !error &&
|
||||||
|
<div className={styles.blankpad}>No alternative titles.</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && hasItems && !error &&
|
||||||
|
<Table columns={columns}>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
items.reverse().map((item) => {
|
||||||
|
return (
|
||||||
|
<MovieTitlesRow
|
||||||
|
key={item.id}
|
||||||
|
{...item}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieTitlesTableContent.propTypes = {
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieTitlesTableContent;
|
@@ -0,0 +1,44 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import MovieTitlesTableContent from './MovieTitlesTableContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.movies,
|
||||||
|
(movies) => {
|
||||||
|
return movies;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
// fetchMovies
|
||||||
|
};
|
||||||
|
|
||||||
|
class MovieTitlesTableContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const movie = this.props.items.filter((obj) => {
|
||||||
|
return obj.id === this.props.movieId;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MovieTitlesTableContent
|
||||||
|
{...this.props}
|
||||||
|
items={movie[0].alternateTitles}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieTitlesTableContentConnector.propTypes = {
|
||||||
|
movieId: PropTypes.number.isRequired,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(MovieTitlesTableContentConnector);
|
@@ -1,34 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import MovieFileEditorModalContentConnector from './MovieFileEditorModalContentConnector';
|
|
||||||
|
|
||||||
function MovieFileEditorModal(props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
onModalClose,
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
isOpen &&
|
|
||||||
<MovieFileEditorModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieFileEditorModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieFileEditorModal;
|
|
@@ -1,284 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
|
||||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
|
||||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
|
||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
|
||||||
import { kinds } from 'Helpers/Props';
|
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
|
||||||
import SelectInput from 'Components/Form/SelectInput';
|
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import MovieFileEditorRow from './MovieFileEditorRow';
|
|
||||||
import styles from './MovieFileEditorModalContent.css';
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
name: 'episodeNumber',
|
|
||||||
label: 'Episode',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'relativePath',
|
|
||||||
label: 'Relative Path',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'airDateUtc',
|
|
||||||
label: 'Air Date',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'language',
|
|
||||||
label: 'Language',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'quality',
|
|
||||||
label: 'Quality',
|
|
||||||
isVisible: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
class MovieFileEditorModalContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
allSelected: false,
|
|
||||||
allUnselected: false,
|
|
||||||
lastToggled: null,
|
|
||||||
selectedState: {},
|
|
||||||
isConfirmDeleteModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
if (hasDifferentItems(prevProps.items, this.props.items)) {
|
|
||||||
this.setState((state) => {
|
|
||||||
return removeOldSelectedState(state, prevProps.items);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
getSelectedIds = () => {
|
|
||||||
const selectedIds = getSelectedIds(this.state.selectedState);
|
|
||||||
|
|
||||||
return selectedIds.reduce((acc, id) => {
|
|
||||||
const matchingItem = this.props.items.find((item) => item.id === id);
|
|
||||||
|
|
||||||
if (matchingItem && !acc.includes(matchingItem.episodeFileId)) {
|
|
||||||
acc.push(matchingItem.episodeFileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onSelectAllChange = ({ value }) => {
|
|
||||||
this.setState(selectAll(this.state.selectedState, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
|
||||||
this.setState((state) => {
|
|
||||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeletePress = () => {
|
|
||||||
this.setState({ isConfirmDeleteModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onConfirmDelete = () => {
|
|
||||||
this.setState({ isConfirmDeleteModalOpen: false });
|
|
||||||
this.props.onDeletePress(this.getSelectedIds());
|
|
||||||
}
|
|
||||||
|
|
||||||
onConfirmDeleteModalClose = () => {
|
|
||||||
this.setState({ isConfirmDeleteModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onLanguageChange = ({ value }) => {
|
|
||||||
const selectedIds = this.getSelectedIds();
|
|
||||||
|
|
||||||
if (!selectedIds.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onLanguageChange(selectedIds, parseInt(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
onQualityChange = ({ value }) => {
|
|
||||||
const selectedIds = this.getSelectedIds();
|
|
||||||
|
|
||||||
if (!selectedIds.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onQualityChange(selectedIds, parseInt(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isDeleting,
|
|
||||||
items,
|
|
||||||
languages,
|
|
||||||
qualities,
|
|
||||||
onModalClose
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
allSelected,
|
|
||||||
allUnselected,
|
|
||||||
selectedState,
|
|
||||||
isConfirmDeleteModalOpen
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const languageOptions = _.reduceRight(languages, (acc, language) => {
|
|
||||||
acc.push({
|
|
||||||
key: language.id,
|
|
||||||
value: language.name
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, [{ key: 'selectLanguage', value: 'Select Language', disabled: true }]);
|
|
||||||
|
|
||||||
const qualityOptions = _.reduceRight(qualities, (acc, quality) => {
|
|
||||||
acc.push({
|
|
||||||
key: quality.id,
|
|
||||||
value: quality.name
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, [{ key: 'selectQuality', value: 'Select Quality', disabled: true }]);
|
|
||||||
|
|
||||||
const hasSelectedFiles = this.getSelectedIds().length > 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
Manage Episodes
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
{
|
|
||||||
!items.length &&
|
|
||||||
<div>
|
|
||||||
No episode files to manage.
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!!items.length &&
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
selectAll={true}
|
|
||||||
allSelected={allSelected}
|
|
||||||
allUnselected={allUnselected}
|
|
||||||
onSelectAllChange={this.onSelectAllChange}
|
|
||||||
>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<MovieFileEditorRow
|
|
||||||
key={item.id}
|
|
||||||
isSelected={selectedState[item.id]}
|
|
||||||
{...item}
|
|
||||||
onSelectedChange={this.onSelectedChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
}
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<div className={styles.actions}>
|
|
||||||
<SpinnerButton
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
isSpinning={isDeleting}
|
|
||||||
isDisabled={!hasSelectedFiles}
|
|
||||||
onPress={this.onDeletePress}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</SpinnerButton>
|
|
||||||
|
|
||||||
<div className={styles.selectInput}>
|
|
||||||
<SelectInput
|
|
||||||
name="language"
|
|
||||||
value="selectLanguage"
|
|
||||||
values={languageOptions}
|
|
||||||
isDisabled={!hasSelectedFiles}
|
|
||||||
onChange={this.onLanguageChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.selectInput}>
|
|
||||||
<SelectInput
|
|
||||||
name="quality"
|
|
||||||
value="selectQuality"
|
|
||||||
values={qualityOptions}
|
|
||||||
isDisabled={!hasSelectedFiles}
|
|
||||||
onChange={this.onQualityChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onPress={onModalClose}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
|
|
||||||
<ConfirmModal
|
|
||||||
isOpen={isConfirmDeleteModalOpen}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
title="Delete Selected Episode Files"
|
|
||||||
message={'Are you sure you want to delete the selected episode files?'}
|
|
||||||
confirmLabel="Delete"
|
|
||||||
onConfirm={this.onConfirmDelete}
|
|
||||||
onCancel={this.onConfirmDeleteModalClose}
|
|
||||||
/>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieFileEditorModalContent.propTypes = {
|
|
||||||
seasonNumber: PropTypes.number,
|
|
||||||
isDeleting: PropTypes.bool.isRequired,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
qualities: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onDeletePress: PropTypes.func.isRequired,
|
|
||||||
onLanguageChange: PropTypes.func.isRequired,
|
|
||||||
onQualityChange: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieFileEditorModalContent;
|
|
28
frontend/src/MovieFile/Editor/MovieFileEditorRow.css
Normal file
28
frontend/src/MovieFile/Editor/MovieFileEditorRow.css
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
.title {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality,
|
||||||
|
.language {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.language {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rejected,
|
||||||
|
.download {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age,
|
||||||
|
.size {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
@@ -1,62 +1,195 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React, { Component } from 'react';
|
||||||
import Label from 'Components/Label';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import SelectQualityModal from 'MovieFile/Quality/SelectQualityModal';
|
||||||
|
import SelectLanguageModal from 'MovieFile/Language/SelectLanguageModal';
|
||||||
|
import * as mediaInfoTypes from 'MovieFile/mediaInfoTypes';
|
||||||
|
import MediaInfoConnector from 'MovieFile/MediaInfoConnector';
|
||||||
|
import MovieFileRowCellPlaceholder from './MovieFileRowCellPlaceholder';
|
||||||
|
import styles from './MovieFileEditorRow.css';
|
||||||
|
|
||||||
function MovieFileEditorRow(props) {
|
class MovieFileEditorRow extends Component {
|
||||||
const {
|
|
||||||
id,
|
|
||||||
relativePath,
|
|
||||||
airDateUtc,
|
|
||||||
language,
|
|
||||||
quality,
|
|
||||||
isSelected,
|
|
||||||
onSelectedChange
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
//
|
||||||
<TableRow>
|
// Lifecycle
|
||||||
<TableSelectCell
|
|
||||||
id={id}
|
|
||||||
isSelected={isSelected}
|
|
||||||
onSelectedChange={onSelectedChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TableRowCell>
|
constructor(props, context) {
|
||||||
{relativePath}
|
super(props, context);
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<RelativeDateCellConnector
|
this.state = {
|
||||||
date={airDateUtc}
|
isSelectQualityModalOpen: false,
|
||||||
/>
|
isSelectLanguageModalOpen: false,
|
||||||
|
isConfirmDeleteModalOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
<TableRowCell>
|
//
|
||||||
<Label>
|
// Listeners
|
||||||
{language.name}
|
|
||||||
</Label>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
onSelectQualityPress = () => {
|
||||||
<MovieQuality
|
this.setState({ isSelectQualityModalOpen: true });
|
||||||
quality={quality}
|
}
|
||||||
|
|
||||||
|
onSelectLanguagePress = () => {
|
||||||
|
this.setState({ isSelectLanguageModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectQualityModalClose = () => {
|
||||||
|
this.setState({ isSelectQualityModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectLanguageModalClose = () => {
|
||||||
|
this.setState({ isSelectLanguageModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeletePress = () => {
|
||||||
|
this.setState({ isConfirmDeleteModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfirmDelete = () => {
|
||||||
|
this.setState({ isConfirmDeleteModalOpen: false });
|
||||||
|
|
||||||
|
this.props.onDeletePress(this.props.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfirmDeleteModalClose = () => {
|
||||||
|
this.setState({ isConfirmDeleteModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
relativePath,
|
||||||
|
quality,
|
||||||
|
languages
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isSelectQualityModalOpen,
|
||||||
|
isSelectLanguageModalOpen,
|
||||||
|
isConfirmDeleteModalOpen
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const showQualityPlaceholder = !quality;
|
||||||
|
|
||||||
|
const showLanguagePlaceholder = !languages;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableRowCell
|
||||||
|
className={styles.relativePath}
|
||||||
|
title={relativePath}
|
||||||
|
>
|
||||||
|
{relativePath}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
<MediaInfoConnector
|
||||||
|
movieFileId={id}
|
||||||
|
type={mediaInfoTypes.VIDEO}
|
||||||
|
/>
|
||||||
|
<MediaInfoConnector
|
||||||
|
movieFileId={id}
|
||||||
|
type={mediaInfoTypes.AUDIO}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCellButton
|
||||||
|
className={styles.language}
|
||||||
|
title="Click to change language"
|
||||||
|
onPress={this.onSelectLanguagePress}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
showLanguagePlaceholder &&
|
||||||
|
<MovieFileRowCellPlaceholder />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!showLanguagePlaceholder && !!languages &&
|
||||||
|
<MovieLanguage
|
||||||
|
className={styles.label}
|
||||||
|
languages={languages}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</TableRowCellButton>
|
||||||
|
|
||||||
|
<TableRowCellButton
|
||||||
|
className={styles.quality}
|
||||||
|
title="Click to change quality"
|
||||||
|
onPress={this.onSelectQualityPress}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
showQualityPlaceholder &&
|
||||||
|
<MovieFileRowCellPlaceholder />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!showQualityPlaceholder && !!quality &&
|
||||||
|
<MovieQuality
|
||||||
|
className={styles.label}
|
||||||
|
quality={quality}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</TableRowCellButton>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.actions}>
|
||||||
|
<IconButton
|
||||||
|
title="Delete file"
|
||||||
|
name={icons.REMOVE}
|
||||||
|
onPress={this.onDeletePress}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isConfirmDeleteModalOpen}
|
||||||
|
ids={[id]}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title="Delete Selected Movie Files"
|
||||||
|
message={'Are you sure you want to delete the selected movie files?'}
|
||||||
|
confirmLabel="Delete"
|
||||||
|
onConfirm={this.onConfirmDelete}
|
||||||
|
onCancel={this.onConfirmDeleteModalClose}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
|
||||||
</TableRow>
|
<SelectQualityModal
|
||||||
);
|
isOpen={isSelectQualityModalOpen}
|
||||||
|
ids={[id]}
|
||||||
|
qualityId={quality ? quality.quality.id : 0}
|
||||||
|
proper={quality ? quality.revision.version > 1 : false}
|
||||||
|
real={quality ? quality.revision.real > 0 : false}
|
||||||
|
onModalClose={this.onSelectQualityModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectLanguageModal
|
||||||
|
isOpen={isSelectLanguageModalOpen}
|
||||||
|
ids={[id]}
|
||||||
|
languageId={languages[0] ? languages[0].id : 0}
|
||||||
|
onModalClose={this.onSelectLanguageModalClose}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieFileEditorRow.propTypes = {
|
MovieFileEditorRow.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
|
size: PropTypes.number.isRequired,
|
||||||
relativePath: PropTypes.string.isRequired,
|
relativePath: PropTypes.string.isRequired,
|
||||||
airDateUtc: PropTypes.string.isRequired,
|
|
||||||
language: PropTypes.object.isRequired,
|
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
isSelected: PropTypes.bool,
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onSelectedChange: PropTypes.func.isRequired
|
mediaInfo: PropTypes.object.isRequired,
|
||||||
|
onDeletePress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieFileEditorRow;
|
export default MovieFileEditorRow;
|
||||||
|
21
frontend/src/MovieFile/Editor/MovieFileEditorTable.js
Normal file
21
frontend/src/MovieFile/Editor/MovieFileEditorTable.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import MovieFileEditorTableContentConnector from './MovieFileEditorTableContentConnector';
|
||||||
|
|
||||||
|
function MovieFileEditorTable(props) {
|
||||||
|
const {
|
||||||
|
movieId
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MovieFileEditorTableContentConnector
|
||||||
|
movieId={movieId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieFileEditorTable.propTypes = {
|
||||||
|
movieId: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieFileEditorTable;
|
@@ -6,3 +6,9 @@
|
|||||||
.selectInput {
|
.selectInput {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blankpad {
|
||||||
|
padding-left:2em;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
86
frontend/src/MovieFile/Editor/MovieFileEditorTableContent.js
Normal file
86
frontend/src/MovieFile/Editor/MovieFileEditorTableContent.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import MovieFileEditorRow from './MovieFileEditorRow';
|
||||||
|
import styles from './MovieFileEditorTableContent.css';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
label: 'Title',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mediainfo',
|
||||||
|
label: 'Media Info',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'languages',
|
||||||
|
label: 'Languages',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quality',
|
||||||
|
label: 'Quality',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'action',
|
||||||
|
label: 'Action',
|
||||||
|
isVisible: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class MovieFileEditorTableContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
!items.length &&
|
||||||
|
<div className={styles.blankpad}>
|
||||||
|
No movie files to manage.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!!items.length &&
|
||||||
|
<Table columns={columns}>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
items.map((item) => {
|
||||||
|
return (
|
||||||
|
<MovieFileEditorRow
|
||||||
|
key={item.id}
|
||||||
|
{...item}
|
||||||
|
onDeletePress={this.props.onDeletePress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieFileEditorTableContent.propTypes = {
|
||||||
|
movieId: PropTypes.number,
|
||||||
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onDeletePress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieFileEditorTableContent;
|
@@ -6,26 +6,30 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import getQualities from 'Utilities/Quality/getQualities';
|
import getQualities from 'Utilities/Quality/getQualities';
|
||||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||||
import { deleteMovieFiles, updateMovieFiles } from 'Store/Actions/movieFileActions';
|
import { deleteMovieFile, updateMovieFiles } from 'Store/Actions/movieFileActions';
|
||||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
import { fetchQualityProfileSchema, fetchLanguages } from 'Store/Actions/settingsActions';
|
||||||
import MovieFileEditorModalContent from './MovieFileEditorModalContent';
|
import MovieFileEditorTableContent from './MovieFileEditorTableContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.movieFiles,
|
(state) => state.movieFiles,
|
||||||
(state) => state.settings.qualityProfiles.schema,
|
(state) => state.settings.languages,
|
||||||
|
(state) => state.settings.qualityProfiles,
|
||||||
createMovieSelector(),
|
createMovieSelector(),
|
||||||
(
|
(
|
||||||
movieFiles,
|
movieFiles,
|
||||||
qualityProfileSchema,
|
languageProfiles,
|
||||||
movie
|
qualityProfiles
|
||||||
) => {
|
) => {
|
||||||
const qualities = getQualities(qualityProfileSchema.items);
|
const languages = languageProfiles.items;
|
||||||
|
const qualities = getQualities(qualityProfiles.schema.items);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: movieFiles.items,
|
items: movieFiles.items,
|
||||||
isDeleting: movieFiles.isDeleting,
|
isDeleting: movieFiles.isDeleting,
|
||||||
isSaving: movieFiles.isSaving,
|
isSaving: movieFiles.isSaving,
|
||||||
|
error: null,
|
||||||
|
languages,
|
||||||
qualities
|
qualities
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -38,22 +42,27 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(fetchQualityProfileSchema());
|
dispatch(fetchQualityProfileSchema());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dispatchFetchLanguages(name, path) {
|
||||||
|
dispatch(fetchLanguages());
|
||||||
|
},
|
||||||
|
|
||||||
dispatchUpdateMovieFiles(updateProps) {
|
dispatchUpdateMovieFiles(updateProps) {
|
||||||
dispatch(updateMovieFiles(updateProps));
|
dispatch(updateMovieFiles(updateProps));
|
||||||
},
|
},
|
||||||
|
|
||||||
onDeletePress(episodeFileIds) {
|
onDeletePress(movieFileId) {
|
||||||
dispatch(deleteMovieFiles({ episodeFileIds }));
|
dispatch(deleteMovieFile(movieFileId));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class MovieFileEditorModalContentConnector extends Component {
|
class MovieFileEditorTableContentConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.props.dispatchFetchLanguages();
|
||||||
this.props.dispatchFetchQualityProfileSchema();
|
this.props.dispatchFetchQualityProfileSchema();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +72,14 @@ class MovieFileEditorModalContentConnector extends Component {
|
|||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onQualityChange = (episodeFileIds, qualityId) => {
|
onLanguageChange = (movieFileIds, languageId) => {
|
||||||
|
const language = _.find(this.props.languages, { id: languageId });
|
||||||
|
// TODO - Placeholder till we implement selection of multiple languages
|
||||||
|
const languages = [language];
|
||||||
|
this.props.dispatchUpdateMovieFiles({ movieFileIds, languages });
|
||||||
|
}
|
||||||
|
|
||||||
|
onQualityChange = (movieFileIds, qualityId) => {
|
||||||
const quality = {
|
const quality = {
|
||||||
quality: _.find(this.props.qualities, { id: qualityId }),
|
quality: _.find(this.props.qualities, { id: qualityId }),
|
||||||
revision: {
|
revision: {
|
||||||
@@ -72,31 +88,34 @@ class MovieFileEditorModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.dispatchUpdateMovieFiles({ episodeFileIds, quality });
|
this.props.dispatchUpdateMovieFiles({ movieFileIds, quality });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
dispatchFetchLanguages,
|
||||||
dispatchFetchQualityProfileSchema,
|
dispatchFetchQualityProfileSchema,
|
||||||
dispatchUpdateMovieFiles,
|
dispatchUpdateMovieFiles,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MovieFileEditorModalContent
|
<MovieFileEditorTableContent
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
onLanguageChange={this.onLanguageChange}
|
||||||
onQualityChange={this.onQualityChange}
|
onQualityChange={this.onQualityChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieFileEditorModalContentConnector.propTypes = {
|
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,
|
||||||
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
||||||
dispatchUpdateMovieFiles: PropTypes.func.isRequired
|
dispatchUpdateMovieFiles: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieFileEditorModalContentConnector);
|
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieFileEditorTableContentConnector);
|
@@ -0,0 +1,7 @@
|
|||||||
|
.placeholder {
|
||||||
|
display: inline-block;
|
||||||
|
margin: -8px 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 25px;
|
||||||
|
border: 2px dashed $dangerColor;
|
||||||
|
}
|
10
frontend/src/MovieFile/Editor/MovieFileRowCellPlaceholder.js
Normal file
10
frontend/src/MovieFile/Editor/MovieFileRowCellPlaceholder.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styles from './MovieFileRowCellPlaceholder.css';
|
||||||
|
|
||||||
|
function MovieFileRowCellPlaceholder() {
|
||||||
|
return (
|
||||||
|
<span className={styles.placeholder} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MovieFileRowCellPlaceholder;
|
37
frontend/src/MovieFile/Language/SelectLanguageModal.js
Normal file
37
frontend/src/MovieFile/Language/SelectLanguageModal.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import SelectLanguageModalContentConnector from './SelectLanguageModalContentConnector';
|
||||||
|
|
||||||
|
class SelectLanguageModal extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<SelectLanguageModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectLanguageModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectLanguageModal;
|
@@ -0,0 +1,87 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { inputTypes } from 'Helpers/Props';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
|
||||||
|
function SelectLanguageModalContent(props) {
|
||||||
|
const {
|
||||||
|
languageId,
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items,
|
||||||
|
onModalClose,
|
||||||
|
onLanguageSelect
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const languageOptions = items.map(( language ) => {
|
||||||
|
return {
|
||||||
|
key: language.id,
|
||||||
|
value: language.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
Manual Import - Select Language
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<div>Unable to load languages</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !error &&
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Language</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="language"
|
||||||
|
value={languageId}
|
||||||
|
values={languageOptions}
|
||||||
|
onChange={onLanguageSelect}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectLanguageModalContent.propTypes = {
|
||||||
|
languageId: PropTypes.number.isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onLanguageSelect: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectLanguageModalContent;
|
@@ -0,0 +1,87 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { fetchLanguages } from 'Store/Actions/settingsActions';
|
||||||
|
import { updateMovieFiles } from 'Store/Actions/movieFileActions';
|
||||||
|
import SelectLanguageModalContent from './SelectLanguageModalContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.languages,
|
||||||
|
(languages) => {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items
|
||||||
|
} = languages;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchFetchLanguages: fetchLanguages,
|
||||||
|
dispatchupdateMovieFiles: updateMovieFiles
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelectLanguageModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
if (!this.props.isPopulated) {
|
||||||
|
this.props.dispatchFetchLanguages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onLanguageSelect = ({ value }) => {
|
||||||
|
const languageId = parseInt(value);
|
||||||
|
|
||||||
|
const language = _.find(this.props.items,
|
||||||
|
(item) => item.id === languageId);
|
||||||
|
const languages = [language];
|
||||||
|
const movieFileIds = this.props.ids;
|
||||||
|
|
||||||
|
this.props.dispatchupdateMovieFiles({ movieFileIds, languages });
|
||||||
|
|
||||||
|
this.props.onModalClose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectLanguageModalContent
|
||||||
|
{...this.props}
|
||||||
|
onLanguageSelect={this.onLanguageSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectLanguageModalContentConnector.propTypes = {
|
||||||
|
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
dispatchFetchLanguages: PropTypes.func.isRequired,
|
||||||
|
dispatchupdateMovieFiles: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(SelectLanguageModalContentConnector);
|
@@ -6,10 +6,10 @@ import MediaInfo from './MediaInfo';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createMovieFileSelector(),
|
createMovieFileSelector(),
|
||||||
(episodeFile) => {
|
(movieFile) => {
|
||||||
if (episodeFile) {
|
if (movieFile) {
|
||||||
return {
|
return {
|
||||||
...episodeFile.mediaInfo
|
...movieFile.mediaInfo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
37
frontend/src/MovieFile/Quality/SelectQualityModal.js
Normal file
37
frontend/src/MovieFile/Quality/SelectQualityModal.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import SelectQualityModalContentConnector from './SelectQualityModalContentConnector';
|
||||||
|
|
||||||
|
class SelectQualityModal extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<SelectQualityModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectQualityModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectQualityModal;
|
166
frontend/src/MovieFile/Quality/SelectQualityModalContent.js
Normal file
166
frontend/src/MovieFile/Quality/SelectQualityModalContent.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
|
||||||
|
class SelectQualityModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
const {
|
||||||
|
qualityId,
|
||||||
|
proper,
|
||||||
|
real
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
qualityId,
|
||||||
|
proper,
|
||||||
|
real
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onQualityChange = ({ value }) => {
|
||||||
|
this.setState({ qualityId: parseInt(value) });
|
||||||
|
}
|
||||||
|
|
||||||
|
onProperChange = ({ value }) => {
|
||||||
|
this.setState({ proper: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
onRealChange = ({ value }) => {
|
||||||
|
this.setState({ real: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
onQualitySelect = () => {
|
||||||
|
this.props.onQualitySelect(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items,
|
||||||
|
onModalClose
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
qualityId,
|
||||||
|
proper,
|
||||||
|
real
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const qualityOptions = items.map(({ id, name }) => {
|
||||||
|
return {
|
||||||
|
key: id,
|
||||||
|
value: name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
Manual Import - Select Quality
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<div>Unable to load qualities</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !error &&
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Quality</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="quality"
|
||||||
|
value={qualityId}
|
||||||
|
values={qualityOptions}
|
||||||
|
onChange={this.onQualityChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Proper</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="proper"
|
||||||
|
value={proper}
|
||||||
|
onChange={this.onProperChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Real</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="real"
|
||||||
|
value={real}
|
||||||
|
onChange={this.onRealChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
kind={kinds.SUCCESS}
|
||||||
|
onPress={this.onQualitySelect}
|
||||||
|
>
|
||||||
|
Select Quality
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectQualityModalContent.propTypes = {
|
||||||
|
qualityId: PropTypes.number.isRequired,
|
||||||
|
proper: PropTypes.bool.isRequired,
|
||||||
|
real: PropTypes.bool.isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onQualitySelect: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectQualityModalContent;
|
@@ -0,0 +1,97 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import getQualities from 'Utilities/Quality/getQualities';
|
||||||
|
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||||
|
import { updateMovieFiles } from 'Store/Actions/movieFileActions';
|
||||||
|
import SelectQualityModalContent from './SelectQualityModalContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.qualityProfiles,
|
||||||
|
(qualityProfiles) => {
|
||||||
|
const {
|
||||||
|
isSchemaFetching: isFetching,
|
||||||
|
isSchemaPopulated: isPopulated,
|
||||||
|
schemaError: error,
|
||||||
|
schema
|
||||||
|
} = qualityProfiles;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items: getQualities(schema.items)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
|
||||||
|
dispatchupdateMovieFiles: updateMovieFiles
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelectQualityModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
if (!this.props.isPopulated) {
|
||||||
|
this.props.dispatchFetchQualityProfileSchema();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onQualitySelect = ({ qualityId, proper, real }) => {
|
||||||
|
const quality = _.find(this.props.items,
|
||||||
|
(item) => item.id === qualityId);
|
||||||
|
|
||||||
|
const revision = {
|
||||||
|
version: proper ? 2 : 1,
|
||||||
|
real: real ? 1 : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const movieFileIds = this.props.ids;
|
||||||
|
|
||||||
|
this.props.dispatchupdateMovieFiles({
|
||||||
|
movieFileIds,
|
||||||
|
quality: {
|
||||||
|
quality,
|
||||||
|
revision
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onModalClose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectQualityModalContent
|
||||||
|
{...this.props}
|
||||||
|
onQualitySelect={this.onQualitySelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectQualityModalContentConnector.propTypes = {
|
||||||
|
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
||||||
|
dispatchupdateMovieFiles: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(SelectQualityModalContentConnector);
|
@@ -38,9 +38,16 @@ export const defaultState = {
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'language',
|
||||||
|
label: 'Language',
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'quality',
|
name: 'quality',
|
||||||
label: 'Quality',
|
label: 'Quality',
|
||||||
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -42,11 +42,13 @@ export const defaultState = {
|
|||||||
{
|
{
|
||||||
name: 'language',
|
name: 'language',
|
||||||
label: 'Language',
|
label: 'Language',
|
||||||
isVisible: false
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'quality',
|
name: 'quality',
|
||||||
label: 'Quality',
|
label: 'Quality',
|
||||||
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -137,9 +137,10 @@ export const actionHandlers = handleThunks({
|
|||||||
},
|
},
|
||||||
|
|
||||||
[UPDATE_MOVIE_FILES]: function(getState, payload, dispatch) {
|
[UPDATE_MOVIE_FILES]: function(getState, payload, dispatch) {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
movieFileIds,
|
movieFileIds,
|
||||||
language,
|
languages,
|
||||||
quality
|
quality
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
@@ -149,8 +150,8 @@ export const actionHandlers = handleThunks({
|
|||||||
movieFileIds
|
movieFileIds
|
||||||
};
|
};
|
||||||
|
|
||||||
if (language) {
|
if (languages) {
|
||||||
data.language = language;
|
data.languages = languages;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quality) {
|
if (quality) {
|
||||||
@@ -169,8 +170,8 @@ export const actionHandlers = handleThunks({
|
|||||||
...movieFileIds.map((id) => {
|
...movieFileIds.map((id) => {
|
||||||
const props = {};
|
const props = {};
|
||||||
|
|
||||||
if (language) {
|
if (languages) {
|
||||||
props.language = language;
|
props.languages = languages;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quality) {
|
if (quality) {
|
||||||
|
@@ -23,27 +23,27 @@ export const defaultState = {
|
|||||||
//
|
//
|
||||||
// Actions Types
|
// Actions Types
|
||||||
|
|
||||||
export const FETCH_SERIES_HISTORY = 'seriesHistory/fetchMovieHistory';
|
export const FETCH_MOVIE_HISTORY = 'movieHistory/fetchMovieHistory';
|
||||||
export const CLEAR_SERIES_HISTORY = 'seriesHistory/clearMovieHistory';
|
export const CLEAR_MOVIE_HISTORY = 'movieHistory/clearMovieHistory';
|
||||||
export const SERIES_HISTORY_MARK_AS_FAILED = 'seriesHistory/seriesHistoryMarkAsFailed';
|
export const MOVIE_HISTORY_MARK_AS_FAILED = 'movieHistory/movieHistoryMarkAsFailed';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
export const fetchMovieHistory = createThunk(FETCH_SERIES_HISTORY);
|
export const fetchMovieHistory = createThunk(FETCH_MOVIE_HISTORY);
|
||||||
export const clearMovieHistory = createAction(CLEAR_SERIES_HISTORY);
|
export const clearMovieHistory = createAction(CLEAR_MOVIE_HISTORY);
|
||||||
export const seriesHistoryMarkAsFailed = createThunk(SERIES_HISTORY_MARK_AS_FAILED);
|
export const movieHistoryMarkAsFailed = createThunk(MOVIE_HISTORY_MARK_AS_FAILED);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Handlers
|
// Action Handlers
|
||||||
|
|
||||||
export const actionHandlers = handleThunks({
|
export const actionHandlers = handleThunks({
|
||||||
|
|
||||||
[FETCH_SERIES_HISTORY]: function(getState, payload, dispatch) {
|
[FETCH_MOVIE_HISTORY]: function(getState, payload, dispatch) {
|
||||||
dispatch(set({ section, isFetching: true }));
|
dispatch(set({ section, isFetching: true }));
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: '/history/series',
|
url: '/history/movie',
|
||||||
data: payload
|
data: payload
|
||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
@@ -70,11 +70,10 @@ export const actionHandlers = handleThunks({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
[SERIES_HISTORY_MARK_AS_FAILED]: function(getState, payload, dispatch) {
|
[MOVIE_HISTORY_MARK_AS_FAILED]: function(getState, payload, dispatch) {
|
||||||
const {
|
const {
|
||||||
historyId,
|
historyId,
|
||||||
seriesId,
|
movieId
|
||||||
seasonNumber
|
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
@@ -86,7 +85,7 @@ export const actionHandlers = handleThunks({
|
|||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
promise.done(() => {
|
promise.done(() => {
|
||||||
dispatch(fetchMovieHistory({ seriesId, seasonNumber }));
|
dispatch(fetchMovieHistory({ movieId }));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -96,7 +95,7 @@ export const actionHandlers = handleThunks({
|
|||||||
|
|
||||||
export const reducers = createHandleActions({
|
export const reducers = createHandleActions({
|
||||||
|
|
||||||
[CLEAR_SERIES_HISTORY]: (state) => {
|
[CLEAR_MOVIE_HISTORY]: (state) => {
|
||||||
return Object.assign({}, state, defaultState);
|
return Object.assign({}, state, defaultState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
74
frontend/src/Store/Actions/movieTitlesActions.js
Normal file
74
frontend/src/Store/Actions/movieTitlesActions.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { batchActions } from 'redux-batched-actions';
|
||||||
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
|
import { set, update } from './baseActions';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
export const section = 'movieTitles';
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
export const defaultState = {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
items: []
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_MOVIE_TITLES = 'movieTitles/fetchMovieTitles';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchMovieTitles = createThunk(FETCH_MOVIE_TITLES);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
export const actionHandlers = handleThunks({
|
||||||
|
|
||||||
|
[FETCH_MOVIE_TITLES]: function(getState, payload, dispatch) {
|
||||||
|
dispatch(set({ section, isFetching: true }));
|
||||||
|
|
||||||
|
const promise = createAjaxRequest({
|
||||||
|
url: '/alttitle',
|
||||||
|
data: payload
|
||||||
|
}).request;
|
||||||
|
|
||||||
|
promise.done((data) => {
|
||||||
|
dispatch(batchActions([
|
||||||
|
update({ section, data }),
|
||||||
|
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: true,
|
||||||
|
error: null
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: xhr
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
export const reducers = createHandleActions({
|
||||||
|
|
||||||
|
}, defaultState, section);
|
@@ -68,6 +68,12 @@ export const defaultState = {
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'languages',
|
||||||
|
label: 'Languages',
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'quality',
|
name: 'quality',
|
||||||
label: 'Quality',
|
label: 'Quality',
|
||||||
|
@@ -11,8 +11,6 @@ import createHandleActions from './Creators/createHandleActions';
|
|||||||
// Variables
|
// Variables
|
||||||
|
|
||||||
export const section = 'releases';
|
export const section = 'releases';
|
||||||
export const episodeSection = 'releases.episode';
|
|
||||||
export const seasonSection = 'releases.season';
|
|
||||||
|
|
||||||
let abortCurrentRequest = null;
|
let abortCurrentRequest = null;
|
||||||
|
|
||||||
@@ -54,28 +52,6 @@ export const defaultState = {
|
|||||||
key: 'all',
|
key: 'all',
|
||||||
label: 'All',
|
label: 'All',
|
||||||
filters: []
|
filters: []
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'season-pack',
|
|
||||||
label: 'Season Pack',
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
key: 'fullSeason',
|
|
||||||
value: true,
|
|
||||||
type: filterTypes.EQUAL
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'not-season-pack',
|
|
||||||
label: 'Not Season Pack',
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
key: 'fullSeason',
|
|
||||||
value: false,
|
|
||||||
type: filterTypes.EQUAL
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -146,20 +122,13 @@ export const defaultState = {
|
|||||||
type: filterBuilderTypes.NUMBER
|
type: filterBuilderTypes.NUMBER
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
selectedFilterKey: 'all'
|
||||||
|
|
||||||
episode: {
|
|
||||||
selectedFilterKey: 'all'
|
|
||||||
},
|
|
||||||
|
|
||||||
season: {
|
|
||||||
selectedFilterKey: 'season-pack'
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const persistState = [
|
export const persistState = [
|
||||||
'releases.selectedFilterKey',
|
'releases.customFilters',
|
||||||
'releases.episode.customFilters',
|
'releases.selectedFilterKey'
|
||||||
'releases.season.customFilters'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -171,8 +140,7 @@ export const SET_RELEASES_SORT = 'releases/setReleasesSort';
|
|||||||
export const CLEAR_RELEASES = 'releases/clearReleases';
|
export const CLEAR_RELEASES = 'releases/clearReleases';
|
||||||
export const GRAB_RELEASE = 'releases/grabRelease';
|
export const GRAB_RELEASE = 'releases/grabRelease';
|
||||||
export const UPDATE_RELEASE = 'releases/updateRelease';
|
export const UPDATE_RELEASE = 'releases/updateRelease';
|
||||||
export const SET_EPISODE_RELEASES_FILTER = 'releases/setEpisodeReleasesFilter';
|
export const SET_RELEASES_FILTER = 'releases/setMovieReleasesFilter';
|
||||||
export const SET_SEASON_RELEASES_FILTER = 'releases/setSeasonReleasesFilter';
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Creators
|
// Action Creators
|
||||||
@@ -183,8 +151,7 @@ export const setReleasesSort = createAction(SET_RELEASES_SORT);
|
|||||||
export const clearReleases = createAction(CLEAR_RELEASES);
|
export const clearReleases = createAction(CLEAR_RELEASES);
|
||||||
export const grabRelease = createThunk(GRAB_RELEASE);
|
export const grabRelease = createThunk(GRAB_RELEASE);
|
||||||
export const updateRelease = createAction(UPDATE_RELEASE);
|
export const updateRelease = createAction(UPDATE_RELEASE);
|
||||||
export const setEpisodeReleasesFilter = createAction(SET_EPISODE_RELEASES_FILTER);
|
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
|
||||||
export const setSeasonReleasesFilter = createAction(SET_SEASON_RELEASES_FILTER);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Helpers
|
// Helpers
|
||||||
@@ -248,13 +215,7 @@ export const actionHandlers = handleThunks({
|
|||||||
export const reducers = createHandleActions({
|
export const reducers = createHandleActions({
|
||||||
|
|
||||||
[CLEAR_RELEASES]: (state) => {
|
[CLEAR_RELEASES]: (state) => {
|
||||||
const {
|
return Object.assign({}, state, defaultState);
|
||||||
episode,
|
|
||||||
season,
|
|
||||||
...otherDefaultState
|
|
||||||
} = defaultState;
|
|
||||||
|
|
||||||
return Object.assign({}, state, otherDefaultState);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[UPDATE_RELEASE]: (state, { payload }) => {
|
[UPDATE_RELEASE]: (state, { payload }) => {
|
||||||
@@ -276,8 +237,7 @@ export const reducers = createHandleActions({
|
|||||||
return newState;
|
return newState;
|
||||||
},
|
},
|
||||||
|
|
||||||
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section),
|
[SET_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(section),
|
||||||
[SET_EPISODE_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(episodeSection),
|
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section)
|
||||||
[SET_SEASON_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(seasonSection)
|
|
||||||
|
|
||||||
}, defaultState, section);
|
}, defaultState, section);
|
||||||
|
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.DecisionEngine
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//remoteMovie.DownloadAllowed = true;
|
remoteMovie.DownloadAllowed = true;
|
||||||
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
decision = GetDecisionForReport(remoteMovie, searchCriteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
namespace Radarr.Api.V2.MovieFiles
|
namespace Radarr.Api.V2.MovieFiles
|
||||||
@@ -6,6 +7,7 @@ namespace Radarr.Api.V2.MovieFiles
|
|||||||
public class MovieFileListResource
|
public class MovieFileListResource
|
||||||
{
|
{
|
||||||
public List<int> MovieFileIds { get; set; }
|
public List<int> MovieFileIds { get; set; }
|
||||||
|
public List<Language> Languages { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -42,10 +42,10 @@ namespace Radarr.Api.V2.MovieFiles
|
|||||||
|
|
||||||
GetResourceById = GetMovieFile;
|
GetResourceById = GetMovieFile;
|
||||||
GetResourceAll = GetMovieFiles;
|
GetResourceAll = GetMovieFiles;
|
||||||
UpdateResource = SetQuality;
|
UpdateResource = SetMovieFile;
|
||||||
DeleteResource = DeleteMovieFile;
|
DeleteResource = DeleteMovieFile;
|
||||||
|
|
||||||
Put["/editor"] = movieFiles => SetQuality();
|
Put["/editor"] = movieFiles => SetMovieFile();
|
||||||
Delete["/bulk"] = movieFiles => DeleteMovieFiles();
|
Delete["/bulk"] = movieFiles => DeleteMovieFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,14 +92,15 @@ namespace Radarr.Api.V2.MovieFiles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetQuality(MovieFileResource movieFileResource)
|
private void SetMovieFile(MovieFileResource movieFileResource)
|
||||||
{
|
{
|
||||||
var movieFile = _mediaFileService.GetMovie(movieFileResource.Id);
|
var movieFile = _mediaFileService.GetMovie(movieFileResource.Id);
|
||||||
movieFile.Quality = movieFileResource.Quality;
|
movieFile.Quality = movieFileResource.Quality;
|
||||||
|
movieFile.Languages = movieFileResource.Languages;
|
||||||
_mediaFileService.Update(movieFile);
|
_mediaFileService.Update(movieFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response SetQuality()
|
private Response SetMovieFile()
|
||||||
{
|
{
|
||||||
var resource = Request.Body.FromJson<MovieFileListResource>();
|
var resource = Request.Body.FromJson<MovieFileListResource>();
|
||||||
var movieFiles = _mediaFileService.GetMovies(resource.MovieFileIds);
|
var movieFiles = _mediaFileService.GetMovies(resource.MovieFileIds);
|
||||||
@@ -111,6 +112,11 @@ namespace Radarr.Api.V2.MovieFiles
|
|||||||
{
|
{
|
||||||
movieFile.Quality = resource.Quality;
|
movieFile.Quality = resource.Quality;
|
||||||
}
|
}
|
||||||
|
if (resource.Languages != null)
|
||||||
|
{
|
||||||
|
movieFile.Languages = resource.Languages;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_mediaFileService.Update(movieFiles);
|
_mediaFileService.Update(movieFiles);
|
||||||
|
Reference in New Issue
Block a user