mirror of https://github.com/lidarr/Lidarr
[UI Work] Interactive Import, More Artist Detail
This commit is contained in:
parent
0054226307
commit
f05332cf6e
|
@ -71,7 +71,7 @@ class ImportArtistRowConnector extends Component {
|
|||
<ImportArtistRow
|
||||
{...this.props}
|
||||
onInputChange={this.onInputChange}
|
||||
onSeriesSelect={this.onSeriesSelect}
|
||||
onArtistSelect={this.onArtistSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -99,10 +99,10 @@ class ImportArtistSelectArtist extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
onSeriesSelect = (foreignArtistId) => {
|
||||
onArtistSelect = (foreignArtistId) => {
|
||||
this.setState({ isOpen: false });
|
||||
|
||||
this.props.onSeriesSelect(foreignArtistId);
|
||||
this.props.onArtistSelect(foreignArtistId);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -117,7 +117,7 @@ class ImportArtistSelectArtist extends Component {
|
|||
error,
|
||||
items,
|
||||
queued,
|
||||
onSeriesSelect
|
||||
onArtistSelect
|
||||
} = this.props;
|
||||
|
||||
const errorMessage = error &&
|
||||
|
@ -233,7 +233,7 @@ class ImportArtistSelectArtist extends Component {
|
|||
overview={item.overview}
|
||||
// year={item.year}
|
||||
// network={item.network}
|
||||
onPress={this.onSeriesSelect}
|
||||
onPress={this.onArtistSelect}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
@ -257,7 +257,7 @@ ImportArtistSelectArtist.propTypes = {
|
|||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
queued: PropTypes.bool.isRequired,
|
||||
onSearchInputChange: PropTypes.func.isRequired,
|
||||
onSeriesSelect: PropTypes.func.isRequired
|
||||
onArtistSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
ImportArtistSelectArtist.defaultProps = {
|
||||
|
|
|
@ -33,7 +33,7 @@ class ImportArtistSelectArtistConnector extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
onSeriesSelect = (foreignArtistId) => {
|
||||
onArtistSelect = (foreignArtistId) => {
|
||||
const {
|
||||
id,
|
||||
items
|
||||
|
@ -53,7 +53,7 @@ class ImportArtistSelectArtistConnector extends Component {
|
|||
<ImportArtistSelectArtist
|
||||
{...this.props}
|
||||
onSearchInputChange={this.onSearchInputChange}
|
||||
onSeriesSelect={this.onSeriesSelect}
|
||||
onArtistSelect={this.onArtistSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ function App({ store, history }) {
|
|||
<PageConnector>
|
||||
<Switch>
|
||||
{/*
|
||||
Series
|
||||
Artist
|
||||
*/}
|
||||
|
||||
<Route
|
||||
|
|
|
@ -112,7 +112,7 @@ class SeriesDetails extends Component {
|
|||
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
||||
}
|
||||
|
||||
onExpandPress = (seasonNumber, isExpanded) => {
|
||||
onExpandPress = (albumId, isExpanded) => {
|
||||
this.setState((state) => {
|
||||
const convertedState = {
|
||||
allSelected: state.allExpanded,
|
||||
|
@ -120,7 +120,7 @@ class SeriesDetails extends Component {
|
|||
selectedState: state.expandedState
|
||||
};
|
||||
|
||||
const newState = toggleSelected(convertedState, [], seasonNumber, isExpanded, false);
|
||||
const newState = toggleSelected(convertedState, [], albumId, isExpanded, false);
|
||||
|
||||
return getExpandedState(newState);
|
||||
});
|
||||
|
@ -136,10 +136,9 @@ class SeriesDetails extends Component {
|
|||
tvMazeId,
|
||||
imdbId,
|
||||
artistName,
|
||||
runtime,
|
||||
ratings,
|
||||
sizeOnDisk,
|
||||
episodeFileCount,
|
||||
trackFileCount,
|
||||
qualityProfileId,
|
||||
monitored,
|
||||
status,
|
||||
|
@ -175,10 +174,10 @@ class SeriesDetails extends Component {
|
|||
|
||||
let episodeFilesCountMessage = 'No episode files';
|
||||
|
||||
if (episodeFileCount === 1) {
|
||||
if (trackFileCount === 1) {
|
||||
episodeFilesCountMessage = '1 episode file';
|
||||
} else if (episodeFileCount > 1) {
|
||||
episodeFilesCountMessage = `${episodeFileCount} episode files`;
|
||||
} else if (trackFileCount > 1) {
|
||||
episodeFilesCountMessage = `${trackFileCount} episode files`;
|
||||
}
|
||||
|
||||
let expandIcon = icons.EXPAND_INDETERMINATE;
|
||||
|
@ -310,13 +309,6 @@ class SeriesDetails extends Component {
|
|||
|
||||
<div className={styles.details}>
|
||||
<div>
|
||||
{
|
||||
!!runtime &&
|
||||
<span className={styles.runtime}>
|
||||
{runtime} Minutes
|
||||
</span>
|
||||
}
|
||||
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
iconSize={20}
|
||||
|
@ -491,10 +483,12 @@ class SeriesDetails extends Component {
|
|||
albums.slice(0).reverse().map((season) => {
|
||||
return (
|
||||
<SeriesDetailsSeasonConnector
|
||||
key={season.seasonNumber}
|
||||
key={season.id}
|
||||
artistId={id}
|
||||
albumId={season.id}
|
||||
statistics={season.statistics}
|
||||
{...season}
|
||||
isExpanded={expandedState[season.seasonNumber]}
|
||||
isExpanded={expandedState[season.id]}
|
||||
onExpandPress={this.onExpandPress}
|
||||
/>
|
||||
);
|
||||
|
@ -548,10 +542,9 @@ SeriesDetails.propTypes = {
|
|||
tvMazeId: PropTypes.number,
|
||||
imdbId: PropTypes.string,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
runtime: PropTypes.number.isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
sizeOnDisk: PropTypes.number.isRequired,
|
||||
episodeFileCount: PropTypes.number,
|
||||
trackFileCount: PropTypes.number,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
|
|
|
@ -16,30 +16,16 @@ function SeriesDetailsLinks(props) {
|
|||
<div className={styles.links}>
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`http://www.thetvdb.com/?tab=series&id=${foreignArtistId}`}
|
||||
to={`https://musicbrainz.org/artist/${foreignArtistId}`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
The TVDB
|
||||
Musicbrainz
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`http://trakt.tv/search/tvdb/${foreignArtistId}?id_type=show`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Trakt
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
{
|
||||
!!tvMazeId &&
|
||||
<Link
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 1 300px;
|
||||
flex: 0 1 600px;
|
||||
}
|
||||
|
||||
.left,
|
||||
|
|
|
@ -23,30 +23,6 @@ import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnecto
|
|||
import EpisodeRowConnector from './EpisodeRowConnector';
|
||||
import styles from './SeriesDetailsSeason.css';
|
||||
|
||||
function getSeasonStatistics(episodes) {
|
||||
let episodeCount = 0;
|
||||
let episodeFileCount = 0;
|
||||
let totalEpisodeCount = 0;
|
||||
|
||||
episodes.forEach((episode) => {
|
||||
if (episode.episodeFileId || (episode.monitored && isBefore(episode.airDateUtc))) {
|
||||
episodeCount++;
|
||||
}
|
||||
|
||||
if (episode.episodeFileId) {
|
||||
episodeFileCount++;
|
||||
}
|
||||
|
||||
totalEpisodeCount++;
|
||||
});
|
||||
|
||||
return {
|
||||
episodeCount,
|
||||
episodeFileCount,
|
||||
totalEpisodeCount
|
||||
};
|
||||
}
|
||||
|
||||
function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) {
|
||||
if (episodeFileCount === episodeCount && episodeCount > 0) {
|
||||
return kinds.SUCCESS;
|
||||
|
@ -89,7 +65,7 @@ class SeriesDetailsSeason extends Component {
|
|||
|
||||
_expandByDefault() {
|
||||
const {
|
||||
seasonNumber,
|
||||
albumId,
|
||||
onExpandPress,
|
||||
items
|
||||
} = this.props;
|
||||
|
@ -99,7 +75,7 @@ class SeriesDetailsSeason extends Component {
|
|||
isAfter(item.airDateUtc, { days: -30 });
|
||||
});
|
||||
|
||||
onExpandPress(seasonNumber, expand && seasonNumber > 0);
|
||||
onExpandPress(albumId, expand && albumId > 0);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -123,11 +99,11 @@ class SeriesDetailsSeason extends Component {
|
|||
|
||||
onExpandPress = () => {
|
||||
const {
|
||||
seasonNumber,
|
||||
albumId,
|
||||
isExpanded
|
||||
} = this.props;
|
||||
|
||||
this.props.onExpandPress(seasonNumber, !isExpanded);
|
||||
this.props.onExpandPress(albumId, !isExpanded);
|
||||
}
|
||||
|
||||
onMonitorEpisodePress = (episodeId, monitored, { shiftKey }) => {
|
||||
|
@ -155,7 +131,10 @@ class SeriesDetailsSeason extends Component {
|
|||
const {
|
||||
artistId,
|
||||
monitored,
|
||||
seasonNumber,
|
||||
title,
|
||||
releaseDate,
|
||||
albumId,
|
||||
statistics,
|
||||
items,
|
||||
columns,
|
||||
isSaving,
|
||||
|
@ -169,10 +148,10 @@ class SeriesDetailsSeason extends Component {
|
|||
} = this.props;
|
||||
|
||||
const {
|
||||
episodeCount,
|
||||
episodeFileCount,
|
||||
totalEpisodeCount
|
||||
} = getSeasonStatistics(items);
|
||||
trackCount,
|
||||
trackFileCount,
|
||||
totalTrackCount
|
||||
} = statistics;
|
||||
|
||||
const {
|
||||
isOrganizeModalOpen,
|
||||
|
@ -194,22 +173,22 @@ class SeriesDetailsSeason extends Component {
|
|||
/>
|
||||
|
||||
{
|
||||
seasonNumber === 0 ?
|
||||
albumId === 0 ?
|
||||
<span className={styles.seasonNumber}>
|
||||
Specials
|
||||
</span> :
|
||||
<span className={styles.seasonNumber}>
|
||||
Season {seasonNumber}
|
||||
{title}
|
||||
</span>
|
||||
}
|
||||
|
||||
<Label
|
||||
title={`${totalEpisodeCount} episodes total. ${episodeFileCount} episodes with files.`}
|
||||
kind={getEpisodeCountKind(monitored, episodeFileCount, episodeCount)}
|
||||
title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`}
|
||||
kind={getEpisodeCountKind(monitored, trackFileCount, trackCount)}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{
|
||||
<span>{episodeFileCount} / {episodeCount}</span>
|
||||
<span>{trackFileCount} / {trackCount}</span>
|
||||
}
|
||||
</Label>
|
||||
</div>
|
||||
|
@ -218,12 +197,7 @@ class SeriesDetailsSeason extends Component {
|
|||
className={styles.expandButton}
|
||||
onPress={this.onExpandPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.expandButtonIcon}
|
||||
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
|
||||
title={isExpanded ? 'Hide episodes' : 'Show episodes'}
|
||||
size={24}
|
||||
/>
|
||||
|
||||
{
|
||||
!isSmallScreen &&
|
||||
<span> </span>
|
||||
|
@ -277,7 +251,7 @@ class SeriesDetailsSeason extends Component {
|
|||
name={icons.EPISODE_FILE}
|
||||
/>
|
||||
|
||||
Manage Episodes
|
||||
Manage Tracks
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</Menu> :
|
||||
|
@ -286,7 +260,7 @@ class SeriesDetailsSeason extends Component {
|
|||
<SpinnerIconButton
|
||||
className={styles.actionButton}
|
||||
name={icons.SEARCH}
|
||||
title="Search for monitored episodes in this seasons"
|
||||
title="Search for album"
|
||||
size={24}
|
||||
isSpinning={isSearching}
|
||||
onPress={onSearchPress}
|
||||
|
@ -295,7 +269,7 @@ class SeriesDetailsSeason extends Component {
|
|||
<IconButton
|
||||
className={styles.actionButton}
|
||||
name={icons.ORGANIZE}
|
||||
title="Preview rename for this season"
|
||||
title="Preview rename for this album"
|
||||
size={24}
|
||||
onPress={this.onOrganizePress}
|
||||
/>
|
||||
|
@ -303,7 +277,7 @@ class SeriesDetailsSeason extends Component {
|
|||
<IconButton
|
||||
className={styles.actionButton}
|
||||
name={icons.EPISODE_FILE}
|
||||
title="Manage episode files in this series"
|
||||
title="Manage track files in this artist"
|
||||
size={24}
|
||||
onPress={this.onManageEpisodesPress}
|
||||
/>
|
||||
|
@ -312,59 +286,17 @@ class SeriesDetailsSeason extends Component {
|
|||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
isExpanded &&
|
||||
<div className={styles.episodes}>
|
||||
{
|
||||
items.length ?
|
||||
<Table
|
||||
columns={columns}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<EpisodeRowConnector
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
{...item}
|
||||
onMonitorEpisodePress={this.onMonitorEpisodePress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table> :
|
||||
|
||||
<div className={styles.noEpisodes}>
|
||||
No episodes in this season
|
||||
</div>
|
||||
}
|
||||
<div className={styles.collapseButtonContainer}>
|
||||
<IconButton
|
||||
name={icons.COLLAPSE}
|
||||
size={20}
|
||||
title="Hide episodes"
|
||||
onPress={this.onExpandPress}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<OrganizePreviewModalConnector
|
||||
isOpen={isOrganizeModalOpen}
|
||||
artistId={artistId}
|
||||
seasonNumber={seasonNumber}
|
||||
albumId={albumId}
|
||||
onModalClose={this.onOrganizeModalClose}
|
||||
/>
|
||||
|
||||
<EpisodeFileEditorModal
|
||||
isOpen={isManageEpisodesOpen}
|
||||
artistId={artistId}
|
||||
seasonNumber={seasonNumber}
|
||||
albumId={albumId}
|
||||
onModalClose={this.onManageEpisodesModalClose}
|
||||
/>
|
||||
</div>
|
||||
|
@ -375,7 +307,10 @@ class SeriesDetailsSeason extends Component {
|
|||
SeriesDetailsSeason.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isSaving: PropTypes.bool,
|
||||
|
@ -390,4 +325,12 @@ SeriesDetailsSeason.propTypes = {
|
|||
onSearchPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
SeriesDetailsSeason.defaultProps = {
|
||||
statistics: {
|
||||
trackFileCount: 0,
|
||||
totalTrackCount: 0,
|
||||
percentOfTracks: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default SeriesDetailsSeason;
|
||||
|
|
|
@ -60,12 +60,12 @@ class SeriesDetailsSeasonConnector extends Component {
|
|||
onMonitorSeasonPress = (monitored) => {
|
||||
const {
|
||||
artistId,
|
||||
seasonNumber
|
||||
albumId
|
||||
} = this.props;
|
||||
|
||||
this.props.toggleSeasonMonitored({
|
||||
artistId,
|
||||
seasonNumber,
|
||||
albumId,
|
||||
monitored
|
||||
});
|
||||
}
|
||||
|
@ -73,13 +73,13 @@ class SeriesDetailsSeasonConnector extends Component {
|
|||
onSearchPress = () => {
|
||||
const {
|
||||
artistId,
|
||||
seasonNumber
|
||||
albumId
|
||||
} = this.props;
|
||||
|
||||
this.props.executeCommand({
|
||||
name: commandNames.SEASON_SEARCH,
|
||||
artistId,
|
||||
seasonNumber
|
||||
albumIds: [albumId]
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ class SeriesDetailsSeasonConnector extends Component {
|
|||
|
||||
SeriesDetailsSeasonConnector.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
toggleSeasonMonitored: PropTypes.func.isRequired,
|
||||
toggleEpisodesMonitored: PropTypes.func.isRequired,
|
||||
setEpisodesTableOption: PropTypes.func.isRequired,
|
||||
|
|
|
@ -15,5 +15,5 @@ export const RENAME_FILES = 'RenameFiles';
|
|||
export const RENAME_ARTIST = 'RenameArtist';
|
||||
export const RESET_API_KEY = 'ResetApiKey';
|
||||
export const RSS_SYNC = 'RssSync';
|
||||
export const SEASON_SEARCH = 'SeasonSearch';
|
||||
export const SEASON_SEARCH = 'AlbumSearch';
|
||||
export const ARTIST_SEARCH = 'ArtistSearch';
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import SelectSeasonModalContentConnector from './SelectSeasonModalContentConnector';
|
||||
import SelectAlbumModalContentConnector from './SelectAlbumModalContentConnector';
|
||||
|
||||
class SelectSeasonModal extends Component {
|
||||
class SelectAlbumModal extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
@ -20,7 +20,7 @@ class SelectSeasonModal extends Component {
|
|||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<SelectSeasonModalContentConnector
|
||||
<SelectAlbumModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
|
@ -29,9 +29,9 @@ class SelectSeasonModal extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
SelectSeasonModal.propTypes = {
|
||||
SelectAlbumModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectSeasonModal;
|
||||
export default SelectAlbumModal;
|
|
@ -5,9 +5,9 @@ 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 SelectSeasonRow from './SelectSeasonRow';
|
||||
import SelectAlbumRow from './SelectAlbumRow';
|
||||
|
||||
class SelectSeasonModalContent extends Component {
|
||||
class SelectAlbumModalContent extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
@ -15,24 +15,25 @@ class SelectSeasonModalContent extends Component {
|
|||
render() {
|
||||
const {
|
||||
items,
|
||||
onSeasonSelect,
|
||||
onAlbumSelect,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Manual Import - Select Season
|
||||
Manual Import - Select Album
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<SelectSeasonRow
|
||||
key={item.seasonNumber}
|
||||
seasonNumber={item.seasonNumber}
|
||||
onSeasonSelect={onSeasonSelect}
|
||||
<SelectAlbumRow
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
onAlbumSelect={onAlbumSelect}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
@ -49,10 +50,10 @@ class SelectSeasonModalContent extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
SelectSeasonModalContent.propTypes = {
|
||||
SelectAlbumModalContent.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSeasonSelect: PropTypes.func.isRequired,
|
||||
onAlbumSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectSeasonModalContent;
|
||||
export default SelectAlbumModalContent;
|
|
@ -1,17 +1,18 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import SelectSeasonModalContent from './SelectSeasonModalContent';
|
||||
import SelectAlbumModalContent from './SelectAlbumModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createArtistSelector(),
|
||||
(series) => {
|
||||
return {
|
||||
items: series.seasons
|
||||
items: series.albums
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -21,16 +22,18 @@ const mapDispatchToProps = {
|
|||
updateInteractiveImportItem
|
||||
};
|
||||
|
||||
class SelectSeasonModalContentConnector extends Component {
|
||||
class SelectAlbumModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSeasonSelect = (seasonNumber) => {
|
||||
onAlbumSelect = (albumId) => {
|
||||
const album = _.find(this.props.items, { id: albumId });
|
||||
|
||||
this.props.ids.forEach((id) => {
|
||||
this.props.updateInteractiveImportItem({
|
||||
id,
|
||||
seasonNumber,
|
||||
album,
|
||||
episodes: []
|
||||
});
|
||||
});
|
||||
|
@ -43,15 +46,15 @@ class SelectSeasonModalContentConnector extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<SelectSeasonModalContent
|
||||
<SelectAlbumModalContent
|
||||
{...this.props}
|
||||
onSeasonSelect={this.onSeasonSelect}
|
||||
onAlbumSelect={this.onAlbumSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectSeasonModalContentConnector.propTypes = {
|
||||
SelectAlbumModalContentConnector.propTypes = {
|
||||
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
@ -59,4 +62,4 @@ SelectSeasonModalContentConnector.propTypes = {
|
|||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(SelectSeasonModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(SelectAlbumModalContentConnector);
|
|
@ -0,0 +1,37 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
import styles from './SelectAlbumRow.css';
|
||||
|
||||
class SelectAlbumRow extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
this.props.onAlbumSelect(this.props.id);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Link
|
||||
className={styles.season}
|
||||
component="div"
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{this.props.title}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectAlbumRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onAlbumSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectAlbumRow;
|
|
@ -37,7 +37,7 @@ class SelectArtistModalContent extends Component {
|
|||
render() {
|
||||
const {
|
||||
items,
|
||||
onSeriesSelect,
|
||||
onArtistSelect,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
|
@ -46,7 +46,7 @@ class SelectArtistModalContent extends Component {
|
|||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Manual Import - Select Series
|
||||
Manual Import - Select Artist
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody
|
||||
|
@ -55,7 +55,7 @@ class SelectArtistModalContent extends Component {
|
|||
>
|
||||
<TextInput
|
||||
className={styles.filterInput}
|
||||
placeholder="Filter series"
|
||||
placeholder="Filter artist"
|
||||
name="filter"
|
||||
value={filter}
|
||||
autoFocus={true}
|
||||
|
@ -65,13 +65,13 @@ class SelectArtistModalContent extends Component {
|
|||
<Scroller className={styles.scroller}>
|
||||
{
|
||||
items.map((item) => {
|
||||
return item.title.toLowerCase().includes(filter) ?
|
||||
return item.artistName.toLowerCase().includes(filter) ?
|
||||
(
|
||||
<SelectArtistRow
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
title={item.title}
|
||||
onSeriesSelect={onSeriesSelect}
|
||||
artistName={item.artistName}
|
||||
onArtistSelect={onArtistSelect}
|
||||
/>
|
||||
) :
|
||||
null;
|
||||
|
@ -92,7 +92,7 @@ class SelectArtistModalContent extends Component {
|
|||
|
||||
SelectArtistModalContent.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSeriesSelect: PropTypes.func.isRequired,
|
||||
onArtistSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
@ -27,15 +27,15 @@ class SelectArtistModalContentConnector extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onSeriesSelect = (artistId) => {
|
||||
const series = _.find(this.props.items, { id: artistId });
|
||||
onArtistSelect = (artistId) => {
|
||||
const artist = _.find(this.props.items, { id: artistId });
|
||||
|
||||
this.props.ids.forEach((id) => {
|
||||
this.props.updateInteractiveImportItem({
|
||||
id,
|
||||
series,
|
||||
seasonNumber: undefined,
|
||||
episodes: []
|
||||
artist,
|
||||
album: undefined,
|
||||
tracks: []
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -49,7 +49,7 @@ class SelectArtistModalContentConnector extends Component {
|
|||
return (
|
||||
<SelectArtistModalContent
|
||||
{...this.props}
|
||||
onSeriesSelect={this.onSeriesSelect}
|
||||
onArtistSelect={this.onArtistSelect}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
.series {
|
||||
.artist {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
}
|
|
@ -9,7 +9,7 @@ class SelectArtistRow extends Component {
|
|||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
this.props.onSeriesSelect(this.props.id);
|
||||
this.props.onArtistSelect(this.props.id);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -18,11 +18,11 @@ class SelectArtistRow extends Component {
|
|||
render() {
|
||||
return (
|
||||
<Link
|
||||
className={styles.series}
|
||||
className={styles.artist}
|
||||
component="div"
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{this.props.title}
|
||||
{this.props.artistName}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ class SelectArtistRow extends Component {
|
|||
|
||||
SelectArtistRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onSeriesSelect: PropTypes.func.isRequired
|
||||
artistName: PropTypes.string.isRequired,
|
||||
onArtistSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectArtistRow;
|
|
@ -15,8 +15,8 @@ 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 SelectArtistModal from 'InteractiveImport/Series/SelectArtistModal';
|
||||
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
|
||||
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
||||
import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
|
||||
import InteractiveImportRow from './InteractiveImportRow';
|
||||
import styles from './InteractiveImportModalContent.css';
|
||||
|
||||
|
@ -28,19 +28,19 @@ const columns = [
|
|||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'series',
|
||||
label: 'Series',
|
||||
name: 'artist',
|
||||
label: 'Artist',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'season',
|
||||
label: 'Season',
|
||||
name: 'album',
|
||||
label: 'Album',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'episodes',
|
||||
label: 'Episode(s)',
|
||||
name: 'tracks',
|
||||
label: 'Track(s)',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
@ -79,7 +79,7 @@ class InteractiveImportModalContent extends Component {
|
|||
selectedState: {},
|
||||
invalidRowsSelected: [],
|
||||
isSelectArtistModalOpen: false,
|
||||
isSelectSeasonModalOpen: false
|
||||
isSelectAlbumModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -131,16 +131,16 @@ class InteractiveImportModalContent extends Component {
|
|||
this.setState({ isSelectArtistModalOpen: true });
|
||||
}
|
||||
|
||||
onSelectSeasonPress = () => {
|
||||
this.setState({ isSelectSeasonModalOpen: true });
|
||||
onSelectAlbumPress = () => {
|
||||
this.setState({ isSelectAlbumModalOpen: true });
|
||||
}
|
||||
|
||||
onSelectArtistModalClose = () => {
|
||||
this.setState({ isSelectArtistModalOpen: false });
|
||||
}
|
||||
|
||||
onSelectSeasonModalClose = () => {
|
||||
this.setState({ isSelectSeasonModalOpen: false });
|
||||
onSelectAlbumModalClose = () => {
|
||||
this.setState({ isSelectAlbumModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -169,7 +169,7 @@ class InteractiveImportModalContent extends Component {
|
|||
selectedState,
|
||||
invalidRowsSelected,
|
||||
isSelectArtistModalOpen,
|
||||
isSelectSeasonModalOpen
|
||||
isSelectAlbumModalOpen
|
||||
} = this.state;
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
|
@ -250,11 +250,11 @@ class InteractiveImportModalContent extends Component {
|
|||
|
||||
<div className={downloadId ? styles.leftButtons : styles.centerButtons}>
|
||||
<Button onPress={this.onSelectArtistPress}>
|
||||
Select Series
|
||||
Select Artist
|
||||
</Button>
|
||||
|
||||
<Button onPress={this.onSelectSeasonPress}>
|
||||
Select Season
|
||||
<Button onPress={this.onSelectAlbumPress}>
|
||||
Select Album
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
@ -284,11 +284,11 @@ class InteractiveImportModalContent extends Component {
|
|||
onModalClose={this.onSelectArtistModalClose}
|
||||
/>
|
||||
|
||||
<SelectSeasonModal
|
||||
isOpen={isSelectSeasonModalOpen}
|
||||
<SelectAlbumModal
|
||||
isOpen={isSelectAlbumModalOpen}
|
||||
ids={selectedIds}
|
||||
artistId={selectedItem && selectedItem.series && selectedItem.series.id}
|
||||
onModalClose={this.onSelectSeasonModalClose}
|
||||
onModalClose={this.onSelectAlbumModalClose}
|
||||
/>
|
||||
</ModalContent>
|
||||
);
|
||||
|
|
|
@ -71,31 +71,32 @@ class InteractiveImportModalContentConnector extends Component {
|
|||
|
||||
if (isSelected) {
|
||||
const {
|
||||
series,
|
||||
seasonNumber,
|
||||
episodes,
|
||||
artist,
|
||||
album,
|
||||
tracks,
|
||||
quality
|
||||
} = item;
|
||||
|
||||
if (!series) {
|
||||
this.setState({ interactiveImportErrorMessage: 'Series must be chosen for each selected file' });
|
||||
if (!artist) {
|
||||
this.setState({ interactiveImportErrorMessage: 'Artist must be chosen for each selected file' });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNaN(seasonNumber)) {
|
||||
this.setState({ interactiveImportErrorMessage: 'Season must be chosen for each selected file' });
|
||||
if (!album) {
|
||||
this.setState({ interactiveImportErrorMessage: 'Album must be chosen for each selected file' });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!episodes || !episodes.length) {
|
||||
this.setState({ interactiveImportErrorMessage: 'One or more episodes must be chosen for each selected file' });
|
||||
if (!tracks || !tracks.length) {
|
||||
this.setState({ interactiveImportErrorMessage: 'One or more tracks must be chosen for each selected file' });
|
||||
return false;
|
||||
}
|
||||
|
||||
files.push({
|
||||
path: item.path,
|
||||
artistId: series.id,
|
||||
episodeIds: _.map(episodes, 'id'),
|
||||
artistId: artist.id,
|
||||
albumId: album.id,
|
||||
trackIds: _.map(tracks, 'id'),
|
||||
quality,
|
||||
downloadId: this.props.downloadId
|
||||
});
|
||||
|
|
|
@ -9,9 +9,9 @@ import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton';
|
|||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||
import SelectArtistModal from 'InteractiveImport/Series/SelectArtistModal';
|
||||
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
|
||||
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
|
||||
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
||||
import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
|
||||
import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
|
||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
|
||||
import styles from './InteractiveImportRow.css';
|
||||
|
@ -26,8 +26,8 @@ class InteractiveImportRow extends Component {
|
|||
|
||||
this.state = {
|
||||
isSelectArtistModalOpen: false,
|
||||
isSelectSeasonModalOpen: false,
|
||||
isSelectEpisodeModalOpen: false,
|
||||
isSelectAlbumModalOpen: false,
|
||||
isSelectTrackModalOpen: false,
|
||||
isSelectQualityModalOpen: false
|
||||
};
|
||||
}
|
||||
|
@ -35,13 +35,13 @@ class InteractiveImportRow extends Component {
|
|||
componentDidMount() {
|
||||
const {
|
||||
id,
|
||||
series,
|
||||
seasonNumber,
|
||||
episodes,
|
||||
artist,
|
||||
album,
|
||||
tracks,
|
||||
quality
|
||||
} = this.props;
|
||||
|
||||
if (series && seasonNumber !== undefined && episodes.length && quality) {
|
||||
if (artist && album !== undefined && tracks.length && quality) {
|
||||
this.props.onSelectedChange({ id, value: true });
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +49,9 @@ class InteractiveImportRow extends Component {
|
|||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
id,
|
||||
series,
|
||||
seasonNumber,
|
||||
episodes,
|
||||
artist,
|
||||
album,
|
||||
tracks,
|
||||
quality,
|
||||
isSelected,
|
||||
onValidRowChange
|
||||
|
@ -61,7 +61,7 @@ class InteractiveImportRow extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
const isValid = !!(series && seasonNumber != null && episodes.length && quality);
|
||||
const isValid = !!(artist && album != null && tracks.length && quality);
|
||||
|
||||
if (isSelected && !isValid) {
|
||||
onValidRowChange(id, false);
|
||||
|
@ -91,12 +91,12 @@ class InteractiveImportRow extends Component {
|
|||
this.setState({ isSelectArtistModalOpen: true });
|
||||
}
|
||||
|
||||
onSelectSeasonPress = () => {
|
||||
this.setState({ isSelectSeasonModalOpen: true });
|
||||
onSelectAlbumPress = () => {
|
||||
this.setState({ isSelectAlbumModalOpen: true });
|
||||
}
|
||||
|
||||
onSelectEpisodePress = () => {
|
||||
this.setState({ isSelectEpisodeModalOpen: true });
|
||||
onSelectTrackPress = () => {
|
||||
this.setState({ isSelectTrackModalOpen: true });
|
||||
}
|
||||
|
||||
onSelectQualityPress = () => {
|
||||
|
@ -108,13 +108,13 @@ class InteractiveImportRow extends Component {
|
|||
this.selectRowAfterChange(changed);
|
||||
}
|
||||
|
||||
onSelectSeasonModalClose = (changed) => {
|
||||
this.setState({ isSelectSeasonModalOpen: false });
|
||||
onSelectAlbumModalClose = (changed) => {
|
||||
this.setState({ isSelectAlbumModalOpen: false });
|
||||
this.selectRowAfterChange(changed);
|
||||
}
|
||||
|
||||
onSelectEpisodeModalClose = (changed) => {
|
||||
this.setState({ isSelectEpisodeModalOpen: false });
|
||||
onSelectTrackModalClose = (changed) => {
|
||||
this.setState({ isSelectTrackModalOpen: false });
|
||||
this.selectRowAfterChange(changed);
|
||||
}
|
||||
|
||||
|
@ -130,9 +130,9 @@ class InteractiveImportRow extends Component {
|
|||
const {
|
||||
id,
|
||||
relativePath,
|
||||
series,
|
||||
seasonNumber,
|
||||
episodes,
|
||||
artist,
|
||||
album,
|
||||
tracks,
|
||||
quality,
|
||||
size,
|
||||
rejections,
|
||||
|
@ -142,18 +142,19 @@ class InteractiveImportRow extends Component {
|
|||
|
||||
const {
|
||||
isSelectArtistModalOpen,
|
||||
isSelectSeasonModalOpen,
|
||||
isSelectEpisodeModalOpen,
|
||||
isSelectAlbumModalOpen,
|
||||
isSelectTrackModalOpen,
|
||||
isSelectQualityModalOpen
|
||||
} = this.state;
|
||||
|
||||
const seriesTitle = series ? series.title : '';
|
||||
const episodeNumbers = episodes.map((episode) => episode.episodeNumber)
|
||||
const seriesTitle = artist ? artist.artistName : '';
|
||||
const albumTitle = album ? album.title : '';
|
||||
const trackNumbers = tracks.map((episode) => episode.trackNumber)
|
||||
.join(', ');
|
||||
|
||||
const showSeriesPlaceholder = isSelected && !series;
|
||||
const showSeasonNumberPlaceholder = isSelected && !!series && isNaN(seasonNumber);
|
||||
const showEpisodeNumbersPlaceholder = isSelected && Number.isInteger(seasonNumber) && !episodes.length;
|
||||
const showSeriesPlaceholder = isSelected && !artist;
|
||||
const showSeasonNumberPlaceholder = isSelected && !!artist && !album;
|
||||
const showEpisodeNumbersPlaceholder = isSelected && !!album && !tracks.length;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
|
@ -179,20 +180,20 @@ class InteractiveImportRow extends Component {
|
|||
</TableRowCellButton>
|
||||
|
||||
<TableRowCellButton
|
||||
isDisabled={!series}
|
||||
onPress={this.onSelectSeasonPress}
|
||||
isDisabled={!artist}
|
||||
onPress={this.onSelectAlbumPress}
|
||||
>
|
||||
{
|
||||
showSeasonNumberPlaceholder ? <InteractiveImportRowCellPlaceholder /> : seasonNumber
|
||||
showSeasonNumberPlaceholder ? <InteractiveImportRowCellPlaceholder /> : albumTitle
|
||||
}
|
||||
</TableRowCellButton>
|
||||
|
||||
<TableRowCellButton
|
||||
isDisabled={!series || isNaN(seasonNumber)}
|
||||
onPress={this.onSelectEpisodePress}
|
||||
isDisabled={!artist || !album}
|
||||
onPress={this.onSelectTrackPress}
|
||||
>
|
||||
{
|
||||
showEpisodeNumbersPlaceholder ? <InteractiveImportRowCellPlaceholder /> : episodeNumbers
|
||||
showEpisodeNumbersPlaceholder ? <InteractiveImportRowCellPlaceholder /> : trackNumbers
|
||||
}
|
||||
</TableRowCellButton>
|
||||
|
||||
|
@ -244,19 +245,19 @@ class InteractiveImportRow extends Component {
|
|||
onModalClose={this.onSelectArtistModalClose}
|
||||
/>
|
||||
|
||||
<SelectSeasonModal
|
||||
isOpen={isSelectSeasonModalOpen}
|
||||
<SelectAlbumModal
|
||||
isOpen={isSelectAlbumModalOpen}
|
||||
ids={[id]}
|
||||
artistId={series && series.id}
|
||||
onModalClose={this.onSelectSeasonModalClose}
|
||||
artistId={artist && artist.id}
|
||||
onModalClose={this.onSelectAlbumModalClose}
|
||||
/>
|
||||
|
||||
<SelectEpisodeModal
|
||||
isOpen={isSelectEpisodeModalOpen}
|
||||
<SelectTrackModal
|
||||
isOpen={isSelectTrackModalOpen}
|
||||
id={id}
|
||||
artistId={series && series.id}
|
||||
seasonNumber={seasonNumber}
|
||||
onModalClose={this.onSelectEpisodeModalClose}
|
||||
artistId={artist && artist.id}
|
||||
albumId={album && album.id}
|
||||
onModalClose={this.onSelectTrackModalClose}
|
||||
/>
|
||||
|
||||
<SelectQualityModal
|
||||
|
@ -276,9 +277,9 @@ class InteractiveImportRow extends Component {
|
|||
InteractiveImportRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
relativePath: PropTypes.string.isRequired,
|
||||
series: PropTypes.object,
|
||||
seasonNumber: PropTypes.number,
|
||||
episodes: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
artist: PropTypes.object,
|
||||
album: PropTypes.object,
|
||||
tracks: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
quality: PropTypes.object,
|
||||
size: PropTypes.number.isRequired,
|
||||
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
@ -288,7 +289,7 @@ InteractiveImportRow.propTypes = {
|
|||
};
|
||||
|
||||
InteractiveImportRow.defaultProps = {
|
||||
episodes: []
|
||||
tracks: []
|
||||
};
|
||||
|
||||
export default InteractiveImportRow;
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
import styles from './SelectSeasonRow.css';
|
||||
|
||||
class SelectSeasonRow extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
this.props.onSeasonSelect(this.props.seasonNumber);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const seasonNumber = this.props.seasonNumber;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={styles.season}
|
||||
component="div"
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{
|
||||
seasonNumber === 0 ? 'Specials' : `Season ${seasonNumber}`
|
||||
}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectSeasonRow.propTypes = {
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
onSeasonSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectSeasonRow;
|
|
@ -1,9 +1,9 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import SelectEpisodeModalContentConnector from './SelectEpisodeModalContentConnector';
|
||||
import SelectTrackModalContentConnector from './SelectTrackModalContentConnector';
|
||||
|
||||
class SelectEpisodeModal extends Component {
|
||||
class SelectTrackModal extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
@ -20,7 +20,7 @@ class SelectEpisodeModal extends Component {
|
|||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<SelectEpisodeModalContentConnector
|
||||
<SelectTrackModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
|
@ -29,9 +29,9 @@ class SelectEpisodeModal extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
SelectEpisodeModal.propTypes = {
|
||||
SelectTrackModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectEpisodeModal;
|
||||
export default SelectTrackModal;
|
|
@ -12,11 +12,11 @@ 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 SelectEpisodeRow from './SelectEpisodeRow';
|
||||
import SelectTrackRow from './SelectTrackRow';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'episodeNumber',
|
||||
name: 'trackNumber',
|
||||
label: '#',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
|
@ -25,15 +25,10 @@ const columns = [
|
|||
name: 'title',
|
||||
label: 'Title',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'airDate',
|
||||
label: 'Air Date',
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class SelectEpisodeModalContent extends Component {
|
||||
class SelectTrackModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
@ -69,8 +64,8 @@ class SelectEpisodeModalContent extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
onEpisodesSelect = () => {
|
||||
this.props.onEpisodesSelect(this.getSelectedIds());
|
||||
onTracksSelect = () => {
|
||||
this.props.onTracksSelect(this.getSelectedIds());
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -94,12 +89,12 @@ class SelectEpisodeModalContent extends Component {
|
|||
selectedState
|
||||
} = this.state;
|
||||
|
||||
const errorMessage = error && error.message || 'Unable to load episodes';
|
||||
const errorMessage = error && error.message || 'Unable to load tracks';
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Manual Import - Select Episode(s)
|
||||
Manual Import - Select Track(s)
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -129,12 +124,11 @@ class SelectEpisodeModalContent extends Component {
|
|||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<SelectEpisodeRow
|
||||
<SelectTrackRow
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
episodeNumber={item.episodeNumber}
|
||||
trackNumber={item.trackNumber}
|
||||
title={item.title}
|
||||
airDate={item.airDate}
|
||||
isSelected={selectedState[item.id]}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
/>
|
||||
|
@ -147,7 +141,7 @@ class SelectEpisodeModalContent extends Component {
|
|||
|
||||
{
|
||||
isPopulated && !items.length &&
|
||||
'No episodes were found for the selected season'
|
||||
'No tracks were found for the selected album'
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
|
@ -158,9 +152,9 @@ class SelectEpisodeModalContent extends Component {
|
|||
|
||||
<Button
|
||||
kind={kinds.SUCCESS}
|
||||
onPress={this.onEpisodesSelect}
|
||||
onPress={this.onTracksSelect}
|
||||
>
|
||||
Select Episodes
|
||||
Select Tracks
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
@ -168,7 +162,7 @@ class SelectEpisodeModalContent extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
SelectEpisodeModalContent.propTypes = {
|
||||
SelectTrackModalContent.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
|
@ -176,8 +170,8 @@ SelectEpisodeModalContent.propTypes = {
|
|||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.string,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onEpisodesSelect: PropTypes.func.isRequired,
|
||||
onTracksSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectEpisodeModalContent;
|
||||
export default SelectTrackModalContent;
|
|
@ -6,7 +6,7 @@ import connectSection from 'Store/connectSection';
|
|||
import { fetchEpisodes, setEpisodesSort, clearEpisodes } from 'Store/Actions/episodeActions';
|
||||
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import SelectEpisodeModalContent from './SelectEpisodeModalContent';
|
||||
import SelectTrackModalContent from './SelectTrackModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
|
@ -24,7 +24,7 @@ const mapDispatchToProps = {
|
|||
updateInteractiveImportItem
|
||||
};
|
||||
|
||||
class SelectEpisodeModalContentConnector extends Component {
|
||||
class SelectTrackModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
@ -32,10 +32,10 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||
componentDidMount() {
|
||||
const {
|
||||
artistId,
|
||||
seasonNumber
|
||||
albumId
|
||||
} = this.props;
|
||||
|
||||
this.props.fetchEpisodes({ artistId, seasonNumber });
|
||||
this.props.fetchEpisodes({ artistId, albumId });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -51,8 +51,8 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||
this.props.setEpisodesSort({ sortKey, sortDirection });
|
||||
}
|
||||
|
||||
onEpisodesSelect = (episodeIds) => {
|
||||
const episodes = _.reduce(this.props.items, (acc, item) => {
|
||||
onTracksSelect = (episodeIds) => {
|
||||
const tracks = _.reduce(this.props.items, (acc, item) => {
|
||||
if (episodeIds.indexOf(item.id) > -1) {
|
||||
acc.push(item);
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||
|
||||
this.props.updateInteractiveImportItem({
|
||||
id: this.props.id,
|
||||
episodes: _.sortBy(episodes, 'episodeNumber')
|
||||
tracks: _.sortBy(tracks, 'trackNumber')
|
||||
});
|
||||
|
||||
this.props.onModalClose(true);
|
||||
|
@ -73,19 +73,19 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<SelectEpisodeModalContent
|
||||
<SelectTrackModalContent
|
||||
{...this.props}
|
||||
onSortPress={this.onSortPress}
|
||||
onEpisodesSelect={this.onEpisodesSelect}
|
||||
onTracksSelect={this.onTracksSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectEpisodeModalContentConnector.propTypes = {
|
||||
SelectTrackModalContentConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchEpisodes: PropTypes.func.isRequired,
|
||||
setEpisodesSort: PropTypes.func.isRequired,
|
||||
|
@ -100,4 +100,4 @@ export default connectSection(
|
|||
undefined,
|
||||
undefined,
|
||||
{ section: 'episodes' }
|
||||
)(SelectEpisodeModalContentConnector);
|
||||
)(SelectTrackModalContentConnector);
|
|
@ -4,7 +4,7 @@ import TableRowButton from 'Components/Table/TableRowButton';
|
|||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
|
||||
class SelectEpisodeRow extends Component {
|
||||
class SelectTrackRow extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
@ -24,9 +24,8 @@ class SelectEpisodeRow extends Component {
|
|||
render() {
|
||||
const {
|
||||
id,
|
||||
episodeNumber,
|
||||
trackNumber,
|
||||
title,
|
||||
airDate,
|
||||
isSelected,
|
||||
onSelectedChange
|
||||
} = this.props;
|
||||
|
@ -40,28 +39,24 @@ class SelectEpisodeRow extends Component {
|
|||
/>
|
||||
|
||||
<TableRowCell>
|
||||
{episodeNumber}
|
||||
{trackNumber}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{title}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{airDate}
|
||||
</TableRowCell>
|
||||
</TableRowButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectEpisodeRow.propTypes = {
|
||||
SelectTrackRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
episodeNumber: PropTypes.number.isRequired,
|
||||
trackNumber: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
airDate: PropTypes.string.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
onSelectedChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectEpisodeRow;
|
||||
export default SelectTrackRow;
|
|
@ -9,7 +9,7 @@ import { updateItem } from './baseActions';
|
|||
const section = 'episodes';
|
||||
|
||||
const episodeActionHandlers = {
|
||||
[types.FETCH_EPISODES]: createFetchHandler(section, '/episode'),
|
||||
[types.FETCH_EPISODES]: createFetchHandler(section, '/track'),
|
||||
|
||||
[types.TOGGLE_EPISODE_MONITORED]: function(payload) {
|
||||
return function(dispatch, getState) {
|
||||
|
|
|
@ -8,10 +8,10 @@ import * as types from './actionTypes';
|
|||
import { set, removeItem, updateItem } from './baseActions';
|
||||
|
||||
const section = 'episodeFiles';
|
||||
const deleteEpisodeFile = createRemoveItemHandler(section, '/episodeFile');
|
||||
const deleteEpisodeFile = createRemoveItemHandler(section, '/trackFile');
|
||||
|
||||
const episodeFileActionHandlers = {
|
||||
[types.FETCH_EPISODE_FILES]: createFetchHandler(section, '/episodeFile'),
|
||||
[types.FETCH_EPISODE_FILES]: createFetchHandler(section, '/trackFile'),
|
||||
|
||||
[types.DELETE_EPISODE_FILE]: function(payload) {
|
||||
return function(dispatch, getState) {
|
||||
|
|
|
@ -13,8 +13,8 @@ export default function(history) {
|
|||
isProduction
|
||||
} = window.Sonarr;
|
||||
|
||||
const dsn = isProduction ? 'https://b80ca60625b443c38b242e0d21681eb7@sentry.sonarr.tv/13' :
|
||||
'https://8dbaacdfe2ff4caf97dc7945aecf9ace@sentry.sonarr.tv/12';
|
||||
const dsn = isProduction ? 'https://c3a5b33e08de4e18b7d0505e942dbc95:e35e6d535b034995a6896022c6bfed04@sentry.io/216290' :
|
||||
'https://c3a5b33e08de4e18b7d0505e942dbc95:e35e6d535b034995a6896022c6bfed04@sentry.io/216290';
|
||||
|
||||
const middlewares = [];
|
||||
|
||||
|
|
|
@ -60,11 +60,6 @@ namespace Lidarr.Api.V3.Artist
|
|||
//public bool SeasonFolder { get; set; }
|
||||
//public bool Monitored { get; set; }
|
||||
|
||||
//public bool UseSceneNumbering { get; set; }
|
||||
//public int Runtime { get; set; }
|
||||
//public int TvdbId { get; set; }
|
||||
//public int TvRageId { get; set; }
|
||||
//public int TvMazeId { get; set; }
|
||||
//public DateTime? FirstAired { get; set; }
|
||||
public DateTime? LastInfoSync { get; set; }
|
||||
////public SeriesTypes SeriesType { get; set; }
|
||||
|
@ -138,10 +133,6 @@ namespace Lidarr.Api.V3.Artist
|
|||
//AlternateTitles
|
||||
SortName = model.SortName,
|
||||
|
||||
//TotalEpisodeCount
|
||||
//EpisodeCount
|
||||
//EpisodeFileCount
|
||||
//SizeOnDisk
|
||||
Status = model.Status,
|
||||
Overview = model.Overview,
|
||||
//NextAiring
|
||||
|
@ -160,12 +151,6 @@ namespace Lidarr.Api.V3.Artist
|
|||
AlbumFolder = model.AlbumFolder,
|
||||
Monitored = model.Monitored,
|
||||
|
||||
//UseSceneNumbering = model.UseSceneNumbering,
|
||||
//Runtime = model.Runtime,
|
||||
//TvdbId = model.TvdbId,
|
||||
//TvRageId = model.TvRageId,
|
||||
//TvMazeId = model.TvMazeId,
|
||||
//FirstAired = model.FirstAired,
|
||||
LastInfoSync = model.LastInfoSync,
|
||||
//SeriesType = model.SeriesType,
|
||||
CleanName = model.CleanName,
|
||||
|
@ -193,10 +178,6 @@ namespace Lidarr.Api.V3.Artist
|
|||
//AlternateTitles
|
||||
SortName = resource.SortName,
|
||||
|
||||
//TotalEpisodeCount
|
||||
//EpisodeCount
|
||||
//EpisodeFileCount
|
||||
//SizeOnDisk
|
||||
Status = resource.Status,
|
||||
Overview = resource.Overview,
|
||||
//NextAiring
|
||||
|
@ -215,12 +196,6 @@ namespace Lidarr.Api.V3.Artist
|
|||
AlbumFolder = resource.AlbumFolder,
|
||||
Monitored = resource.Monitored,
|
||||
|
||||
//UseSceneNumbering = resource.UseSceneNumbering,
|
||||
//Runtime = resource.Runtime,
|
||||
//TvdbId = resource.TvdbId,
|
||||
//TvRageId = resource.TvRageId,
|
||||
//TvMazeId = resource.TvMazeId,
|
||||
//FirstAired = resource.FirstAired,
|
||||
LastInfoSync = resource.LastInfoSync,
|
||||
//SeriesType = resource.SeriesType,
|
||||
CleanName = resource.CleanName,
|
||||
|
|
|
@ -3,6 +3,7 @@ using NzbDrone.Core.DecisionEngine;
|
|||
using NzbDrone.Core.MediaFiles.TrackImport.Manual;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using Lidarr.Api.V3.Artist;
|
||||
using Lidarr.Api.V3.Albums;
|
||||
using Lidarr.Api.V3.Tracks;
|
||||
using Lidarr.Http.REST;
|
||||
using System.Collections.Generic;
|
||||
|
@ -16,9 +17,9 @@ namespace Lidarr.Api.V3.ManualImport
|
|||
public string RelativePath { get; set; }
|
||||
public string Name { get; set; }
|
||||
public long Size { get; set; }
|
||||
//public ArtistResource Artist { get; set; }
|
||||
public int? SeasonNumber { get; set; }
|
||||
public List<TrackResource> Episodes { get; set; }
|
||||
public ArtistResource Artist { get; set; }
|
||||
public AlbumResource Album { get; set; }
|
||||
public List<TrackResource> Tracks { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public int QualityWeight { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
|
@ -38,9 +39,9 @@ namespace Lidarr.Api.V3.ManualImport
|
|||
RelativePath = model.RelativePath,
|
||||
Name = model.Name,
|
||||
Size = model.Size,
|
||||
//Artist = model.,
|
||||
SeasonNumber = model.SeasonNumber,
|
||||
//Episodes = model.Episodes.ToResource(),
|
||||
Artist = model.Artist.ToResource(),
|
||||
Album = model.Album.ToResource(),
|
||||
Tracks = model.Tracks.ToResource(),
|
||||
Quality = model.Quality,
|
||||
//QualityWeight
|
||||
DownloadId = model.DownloadId,
|
||||
|
|
|
@ -15,9 +15,6 @@ namespace NzbDrone.Api.ManualImport
|
|||
public string RelativePath { get; set; }
|
||||
public string Name { get; set; }
|
||||
public long Size { get; set; }
|
||||
public SeriesResource Series { get; set; }
|
||||
public int? SeasonNumber { get; set; }
|
||||
public List<EpisodeResource> Episodes { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public int QualityWeight { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
|
@ -38,9 +35,6 @@ namespace NzbDrone.Api.ManualImport
|
|||
RelativePath = model.RelativePath,
|
||||
Name = model.Name,
|
||||
Size = model.Size,
|
||||
Series = model.Series.ToResource(),
|
||||
SeasonNumber = model.SeasonNumber,
|
||||
Episodes = model.Episodes.ToResource(),
|
||||
Quality = model.Quality,
|
||||
//QualityWeight
|
||||
DownloadId = model.DownloadId,
|
||||
|
|
|
@ -84,7 +84,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
trackFile.Size = _diskProvider.GetFileSize(localTrack.Path);
|
||||
trackFile.Quality = localTrack.Quality;
|
||||
trackFile.MediaInfo = localTrack.MediaInfo;
|
||||
trackFile.AlbumId = _albumRepository.FindByArtistAndName(localTrack.Artist.Name, Parser.Parser.CleanArtistTitle(localTrack.ParsedTrackInfo.AlbumTitle)).Id;
|
||||
trackFile.AlbumId = localTrack.Album.Id;
|
||||
trackFile.ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup;
|
||||
trackFile.Tracks = localTrack.Tracks;
|
||||
trackFile.Language = localTrack.Language;
|
||||
|
|
|
@ -6,8 +6,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
public class ManualImportFile
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
public List<int> EpisodeIds { get; set; }
|
||||
public int ArtistId { get; set; }
|
||||
public int AlbumId { get; set; }
|
||||
public List<int> TrackIds { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||
{
|
||||
|
@ -11,9 +11,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
public string RelativePath { get; set; }
|
||||
public string Name { get; set; }
|
||||
public long Size { get; set; }
|
||||
public Series Series { get; set; }
|
||||
public int? SeasonNumber { get; set; }
|
||||
public List<Episode> Episodes { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
public Album Album { get; set; }
|
||||
public List<Track> Tracks { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
|
|
|
@ -14,7 +14,7 @@ using NzbDrone.Core.Messaging.Commands;
|
|||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||
{
|
||||
|
@ -29,10 +29,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
private readonly IParsingService _parsingService;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
||||
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||
private readonly IImportApprovedTracks _importApprovedTracks;
|
||||
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
@ -42,10 +43,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
IParsingService parsingService,
|
||||
IDiskScanService diskScanService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
ISeriesService seriesService,
|
||||
IEpisodeService episodeService,
|
||||
IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
ITrackService trackService,
|
||||
IVideoFileInfoReader videoFileInfoReader,
|
||||
IImportApprovedEpisodes importApprovedEpisodes,
|
||||
IImportApprovedTracks importApprovedTracks,
|
||||
ITrackedDownloadService trackedDownloadService,
|
||||
IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
||||
IEventAggregator eventAggregator,
|
||||
|
@ -55,10 +57,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
_parsingService = parsingService;
|
||||
_diskScanService = diskScanService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_seriesService = seriesService;
|
||||
_episodeService = episodeService;
|
||||
_artistService = artistService;
|
||||
_albumService = albumService;
|
||||
_trackService = trackService;
|
||||
_videoFileInfoReader = videoFileInfoReader;
|
||||
_importApprovedEpisodes = importApprovedEpisodes;
|
||||
_importApprovedTracks = importApprovedTracks;
|
||||
_trackedDownloadService = trackedDownloadService;
|
||||
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
||||
_eventAggregator = eventAggregator;
|
||||
|
@ -94,179 +97,176 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
|||
|
||||
private List<ManualImportItem> ProcessFolder(string folder, string downloadId)
|
||||
{
|
||||
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
|
||||
//var directoryInfo = new DirectoryInfo(folder);
|
||||
//var series = _parsingService.GetSeries(directoryInfo.Name);
|
||||
var directoryInfo = new DirectoryInfo(folder);
|
||||
var artist = _parsingService.GetArtist(directoryInfo.Name);
|
||||
|
||||
//if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
//{
|
||||
// var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
// series = trackedDownload.RemoteEpisode.Series;
|
||||
//}
|
||||
if (artist == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
artist = trackedDownload.RemoteAlbum.Artist;
|
||||
}
|
||||
|
||||
//if (series == null)
|
||||
//{
|
||||
// var files = _diskScanService.GetVideoFiles(folder);
|
||||
if (artist == null)
|
||||
{
|
||||
var files = _diskScanService.GetAudioFiles(folder);
|
||||
|
||||
// return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
|
||||
//}
|
||||
return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
|
||||
}
|
||||
|
||||
//var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
|
||||
//var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
|
||||
//var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder));
|
||||
var folderInfo = Parser.Parser.ParseMusicTitle(directoryInfo.Name);
|
||||
var artistFiles = _diskScanService.GetAudioFiles(folder).ToList();
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(artistFiles, artist, folderInfo);
|
||||
|
||||
//return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList();
|
||||
return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList();
|
||||
}
|
||||
|
||||
private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
|
||||
{
|
||||
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
|
||||
//if (folder.IsNullOrWhiteSpace())
|
||||
//{
|
||||
// folder = new FileInfo(file).Directory.FullName;
|
||||
//}
|
||||
if (folder.IsNullOrWhiteSpace())
|
||||
{
|
||||
folder = new FileInfo(file).Directory.FullName;
|
||||
}
|
||||
|
||||
//var relativeFile = folder.GetRelativePath(file);
|
||||
var relativeFile = folder.GetRelativePath(file);
|
||||
|
||||
//var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
|
||||
var artist = _parsingService.GetArtist(relativeFile.Split('\\', '/')[0]);
|
||||
|
||||
//if (series == null)
|
||||
//{
|
||||
// series = _parsingService.GetSeries(relativeFile);
|
||||
//}
|
||||
if (artist == null)
|
||||
{
|
||||
artist = _parsingService.GetArtistFromTag(file);
|
||||
}
|
||||
|
||||
//if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
//{
|
||||
// var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
// series = trackedDownload.RemoteEpisode.Series;
|
||||
//}
|
||||
if (artist == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
artist = trackedDownload.RemoteAlbum.Artist;
|
||||
}
|
||||
|
||||
//if (series == null)
|
||||
//{
|
||||
// var localEpisode = new LocalEpisode();
|
||||
// localEpisode.Path = file;
|
||||
// localEpisode.Quality = QualityParser.ParseQuality(file);
|
||||
// localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
if (artist == null)
|
||||
{
|
||||
var localTrack = new LocalTrack();
|
||||
localTrack.Path = file;
|
||||
localTrack.Quality = QualityParser.ParseQuality(file);
|
||||
localTrack.Size = _diskProvider.GetFileSize(file);
|
||||
|
||||
// return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
|
||||
//}
|
||||
return MapItem(new ImportDecision(localTrack, new Rejection("Unknown Artist")), folder, downloadId);
|
||||
}
|
||||
|
||||
//var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
|
||||
// series, null, SceneSource(series, folder));
|
||||
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file },
|
||||
artist, null);
|
||||
|
||||
//return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
|
||||
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
|
||||
}
|
||||
|
||||
private bool SceneSource(Series series, string folder)
|
||||
private bool SceneSource(Artist artist, string folder)
|
||||
{
|
||||
return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder));
|
||||
return !(artist.Path.PathEquals(folder) || artist.Path.IsParentPath(folder));
|
||||
}
|
||||
|
||||
private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
|
||||
{
|
||||
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
|
||||
//var item = new ManualImportItem();
|
||||
var item = new ManualImportItem();
|
||||
|
||||
//item.Path = decision.LocalEpisode.Path;
|
||||
//item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
|
||||
//item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
|
||||
//item.DownloadId = downloadId;
|
||||
item.Path = decision.LocalTrack.Path;
|
||||
item.RelativePath = folder.GetRelativePath(decision.LocalTrack.Path);
|
||||
item.Name = Path.GetFileNameWithoutExtension(decision.LocalTrack.Path);
|
||||
item.DownloadId = downloadId;
|
||||
|
||||
//if (decision.LocalEpisode.Series != null)
|
||||
//{
|
||||
// item.Series = decision.LocalEpisode.Series;
|
||||
//}
|
||||
if (decision.LocalTrack.Artist != null)
|
||||
{
|
||||
item.Artist = decision.LocalTrack.Artist;
|
||||
}
|
||||
|
||||
//if (decision.LocalEpisode.Episodes.Any())
|
||||
//{
|
||||
// item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
|
||||
// item.Episodes = decision.LocalEpisode.Episodes;
|
||||
//}
|
||||
if (decision.LocalTrack.Tracks.Any())
|
||||
{
|
||||
item.Tracks = decision.LocalTrack.Tracks;
|
||||
}
|
||||
|
||||
//item.Quality = decision.LocalEpisode.Quality;
|
||||
//item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
|
||||
//item.Rejections = decision.Rejections;
|
||||
item.Quality = decision.LocalTrack.Quality;
|
||||
item.Size = _diskProvider.GetFileSize(decision.LocalTrack.Path);
|
||||
item.Rejections = decision.Rejections;
|
||||
|
||||
//return item;
|
||||
return item;
|
||||
}
|
||||
|
||||
public void Execute(ManualImportCommand message)
|
||||
{
|
||||
_logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode);
|
||||
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
|
||||
|
||||
//var imported = new List<ImportResult>();
|
||||
//var importedTrackedDownload = new List<ManuallyImportedFile>();
|
||||
var imported = new List<ImportResult>();
|
||||
var importedTrackedDownload = new List<ManuallyImportedFile>();
|
||||
|
||||
//for (int i = 0; i < message.Files.Count; i++)
|
||||
//{
|
||||
// _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
|
||||
for (int i = 0; i < message.Files.Count; i++)
|
||||
{
|
||||
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
|
||||
|
||||
// var file = message.Files[i];
|
||||
// var series = _seriesService.GetSeries(file.SeriesId);
|
||||
// var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
|
||||
// var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
|
||||
// var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
|
||||
// var existingFile = series.Path.IsParentPath(file.Path);
|
||||
var file = message.Files[i];
|
||||
var artist = _artistService.GetArtist(file.ArtistId);
|
||||
var album = _albumService.GetAlbum(file.AlbumId);
|
||||
var tracks = _trackService.GetTracks(file.TrackIds);
|
||||
var parsedTrackInfo = Parser.Parser.ParseMusicPath(file.Path) ?? new ParsedTrackInfo();
|
||||
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
|
||||
var existingFile = artist.Path.IsParentPath(file.Path);
|
||||
|
||||
// var localEpisode = new LocalEpisode
|
||||
// {
|
||||
// ExistingFile = false,
|
||||
// Episodes = episodes,
|
||||
// MediaInfo = mediaInfo,
|
||||
// ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
// Path = file.Path,
|
||||
// Quality = file.Quality,
|
||||
// Series = series,
|
||||
// Size = 0
|
||||
// };
|
||||
var localTrack = new LocalTrack
|
||||
{
|
||||
ExistingFile = false,
|
||||
Tracks = tracks,
|
||||
MediaInfo = mediaInfo,
|
||||
ParsedTrackInfo = parsedTrackInfo,
|
||||
Path = file.Path,
|
||||
Quality = file.Quality,
|
||||
Artist = artist,
|
||||
Album = album,
|
||||
Size = 0
|
||||
};
|
||||
|
||||
// //TODO: Cleanup non-tracked downloads
|
||||
//TODO: Cleanup non-tracked downloads
|
||||
|
||||
// var importDecision = new ImportDecision(localEpisode);
|
||||
var importDecision = new ImportDecision(localTrack);
|
||||
|
||||
// if (file.DownloadId.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
||||
// }
|
||||
if (file.DownloadId.IsNullOrWhiteSpace())
|
||||
{
|
||||
imported.AddRange(_importApprovedTracks.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
||||
}
|
||||
|
||||
// else
|
||||
// {
|
||||
// var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
|
||||
// var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
|
||||
else
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
|
||||
var importResult = _importApprovedTracks.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
|
||||
|
||||
// imported.Add(importResult);
|
||||
imported.Add(importResult);
|
||||
|
||||
// importedTrackedDownload.Add(new ManuallyImportedFile
|
||||
// {
|
||||
// TrackedDownload = trackedDownload,
|
||||
// ImportResult = importResult
|
||||
// });
|
||||
// }
|
||||
//}
|
||||
importedTrackedDownload.Add(new ManuallyImportedFile
|
||||
{
|
||||
TrackedDownload = trackedDownload,
|
||||
ImportResult = importResult
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//_logger.ProgressTrace("Manually imported {0} files", imported.Count);
|
||||
_logger.ProgressTrace("Manually imported {0} files", imported.Count);
|
||||
|
||||
//foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
|
||||
//{
|
||||
// var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
|
||||
foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
|
||||
{
|
||||
var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
|
||||
|
||||
// if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
|
||||
// {
|
||||
// if (_downloadedEpisodesImportService.ShouldDeleteFolder(
|
||||
// new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
|
||||
// trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
// {
|
||||
// _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
|
||||
// }
|
||||
// }
|
||||
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
|
||||
{
|
||||
if (_downloadedEpisodesImportService.ShouldDeleteFolder(
|
||||
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
|
||||
trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
{
|
||||
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
// if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
|
||||
// {
|
||||
// trackedDownload.State = TrackedDownloadStage.Imported;
|
||||
// _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
// }
|
||||
//}
|
||||
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Count))
|
||||
{
|
||||
trackedDownload.State = TrackedDownloadStage.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ namespace NzbDrone.Core.Parser.Model
|
|||
public string ArtistTitle { get; set; }
|
||||
public string AlbumTitle { get; set; }
|
||||
public ArtistTitleInfo ArtistTitleInfo { get; set; }
|
||||
public string ArtistMBId { get; set; }
|
||||
public string AlbumMBId { get; set; }
|
||||
public string TrackMBId { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public int[] TrackNumbers { get; set; }
|
||||
public Language Language { get; set; }
|
||||
|
@ -26,21 +29,18 @@ namespace NzbDrone.Core.Parser.Model
|
|||
TrackNumbers = new int[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string episodeString = "[Unknown Track]";
|
||||
string trackString = "[Unknown Track]";
|
||||
|
||||
|
||||
if (TrackNumbers != null && TrackNumbers.Any())
|
||||
{
|
||||
episodeString = string.Format("T{0}", string.Join("-", TrackNumbers.Select(c => c.ToString("00"))));
|
||||
trackString = string.Format("T{0}", string.Join("-", TrackNumbers.Select(c => c.ToString("00"))));
|
||||
}
|
||||
|
||||
|
||||
return string.Format("{0} - {1} {2}", ArtistTitle, episodeString, Quality);
|
||||
return string.Format("{0} - {1} {2}", ArtistTitle, trackString, Quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -336,9 +336,16 @@ namespace NzbDrone.Core.Parser
|
|||
var trackName = file.Tag.Title;
|
||||
var trackNumber = file.Tag.Track;
|
||||
|
||||
var artist = file.Tag.FirstAlbumArtist;
|
||||
|
||||
if (artist.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
artist = file.Tag.FirstPerformer;
|
||||
}
|
||||
|
||||
var artistTitleInfo = new ArtistTitleInfo
|
||||
{
|
||||
Title = file.Tag.Title,
|
||||
Title = artist,
|
||||
Year = (int)file.Tag.Year
|
||||
};
|
||||
|
||||
|
@ -348,7 +355,10 @@ namespace NzbDrone.Core.Parser
|
|||
{
|
||||
Language = Language.English, //TODO Parse from Tag/Mediainfo
|
||||
AlbumTitle = file.Tag.Album,
|
||||
ArtistTitle = file.Tag.FirstAlbumArtist,
|
||||
ArtistTitle = artist,
|
||||
ArtistMBId = file.Tag.MusicBrainzArtistId,
|
||||
AlbumMBId = file.Tag.MusicBrainzReleaseId,
|
||||
TrackMBId = file.Tag.MusicBrainzReleaseType,
|
||||
Quality = QualityParser.ParseQuality(trackName),
|
||||
TrackNumbers = temp,
|
||||
ArtistTitleInfo = artistTitleInfo,
|
||||
|
|
|
@ -18,6 +18,8 @@ namespace NzbDrone.Core.Parser
|
|||
LocalEpisode GetLocalEpisode(string filename, Series series);
|
||||
LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
||||
Series GetSeries(string title);
|
||||
Artist GetArtist(string title);
|
||||
Artist GetArtistFromTag(string file);
|
||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
|
||||
RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null);
|
||||
|
@ -137,6 +139,44 @@ namespace NzbDrone.Core.Parser
|
|||
return series;
|
||||
}
|
||||
|
||||
public Artist GetArtist(string title)
|
||||
{
|
||||
var parsedAlbumInfo = Parser.ParseAlbumTitle(title);
|
||||
|
||||
if (parsedAlbumInfo == null || parsedAlbumInfo.ArtistName.IsNullOrWhiteSpace())
|
||||
{
|
||||
return _artistService.FindByName(title);
|
||||
}
|
||||
|
||||
return _artistService.FindByName(parsedAlbumInfo.ArtistName);
|
||||
|
||||
}
|
||||
|
||||
public Artist GetArtistFromTag(string file)
|
||||
{
|
||||
var parsedTrackInfo = Parser.ParseMusicPath(file);
|
||||
|
||||
var artist = new Artist();
|
||||
|
||||
if (parsedTrackInfo.ArtistMBId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
artist = _artistService.FindById(parsedTrackInfo.ArtistMBId);
|
||||
|
||||
if (artist != null)
|
||||
{
|
||||
return artist;
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedTrackInfo == null || parsedTrackInfo.ArtistTitle.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _artistService.FindByName(parsedTrackInfo.ArtistTitle);
|
||||
|
||||
}
|
||||
|
||||
[System.Obsolete("Used for sonarr, not lidarr")]
|
||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
|
@ -670,10 +710,12 @@ namespace NzbDrone.Core.Parser
|
|||
}
|
||||
|
||||
var tracks = GetTracks(parsedTrackInfo, artist);
|
||||
var album = _albumService.FindByTitle(artist.Id, parsedTrackInfo.AlbumTitle);
|
||||
|
||||
return new LocalTrack
|
||||
{
|
||||
Artist = artist,
|
||||
Album = album,
|
||||
Quality = parsedTrackInfo.Quality,
|
||||
Language = parsedTrackInfo.Language,
|
||||
Tracks = tracks,
|
||||
|
|
Loading…
Reference in New Issue