mirror of https://github.com/lidarr/Lidarr
[UI Work] Artist Detail, Album Dialog, Album Search, Album Missing Search
This commit is contained in:
parent
5fec72395c
commit
0a7f18e843
|
@ -10,13 +10,13 @@ class AlbumStudioAlbum extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onSeasonMonitoredPress = () => {
|
||||
onAlbumMonitoredPress = () => {
|
||||
const {
|
||||
id,
|
||||
monitored
|
||||
} = this.props;
|
||||
|
||||
this.props.onSeasonMonitoredPress(id, !monitored);
|
||||
this.props.onAlbumMonitoredPress(id, !monitored);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -43,7 +43,7 @@ class AlbumStudioAlbum extends Component {
|
|||
<MonitorToggleButton
|
||||
monitored={monitored}
|
||||
isSaving={isSaving}
|
||||
onPress={this.onSeasonMonitoredPress}
|
||||
onPress={this.onAlbumMonitoredPress}
|
||||
/>
|
||||
|
||||
<span>
|
||||
|
@ -75,7 +75,7 @@ AlbumStudioAlbum.propTypes = {
|
|||
monitored: PropTypes.bool.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
onSeasonMonitoredPress: PropTypes.func.isRequired
|
||||
onAlbumMonitoredPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
AlbumStudioAlbum.defaultProps = {
|
||||
|
|
|
@ -26,8 +26,8 @@ class AlbumStudioRow extends Component {
|
|||
isSaving,
|
||||
isSelected,
|
||||
onSelectedChange,
|
||||
onSeriesMonitoredPress,
|
||||
onSeasonMonitoredPress
|
||||
onArtistMonitoredPress,
|
||||
onAlbumMonitoredPress
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -41,7 +41,7 @@ class AlbumStudioRow extends Component {
|
|||
<TableRowCell className={styles.status}>
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={status === 'ended' ? icons.SERIES_ENDED : icons.SERIES_CONTINUING}
|
||||
name={status === 'ended' ? icons.ARTIST_ENDED : icons.ARTIST_CONTINUING}
|
||||
title={status === 'ended' ? 'Ended' : 'Continuing'}
|
||||
|
||||
/>
|
||||
|
@ -58,7 +58,7 @@ class AlbumStudioRow extends Component {
|
|||
<MonitorToggleButton
|
||||
monitored={monitored}
|
||||
isSaving={isSaving}
|
||||
onPress={onSeriesMonitoredPress}
|
||||
onPress={onArtistMonitoredPress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
|
@ -69,7 +69,7 @@ class AlbumStudioRow extends Component {
|
|||
<AlbumStudioAlbum
|
||||
key={season.id}
|
||||
{...season}
|
||||
onSeasonMonitoredPress={onSeasonMonitoredPress}
|
||||
onAlbumMonitoredPress={onAlbumMonitoredPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
@ -90,8 +90,8 @@ AlbumStudioRow.propTypes = {
|
|||
isSaving: PropTypes.bool.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onSeriesMonitoredPress: PropTypes.func.isRequired,
|
||||
onSeasonMonitoredPress: PropTypes.func.isRequired
|
||||
onArtistMonitoredPress: PropTypes.func.isRequired,
|
||||
onAlbumMonitoredPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
AlbumStudioRow.defaultProps = {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||
import { createSelector } from 'reselect';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import { toggleSeriesMonitored, toggleSeasonMonitored } from 'Store/Actions/artistActions';
|
||||
import { toggleEpisodeMonitored } from 'Store/Actions/episodeActions';
|
||||
import AlbumStudioRow from './AlbumStudioRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
@ -25,7 +26,8 @@ function createMapStateToProps() {
|
|||
|
||||
const mapDispatchToProps = {
|
||||
toggleSeriesMonitored,
|
||||
toggleSeasonMonitored
|
||||
toggleSeasonMonitored,
|
||||
toggleEpisodeMonitored
|
||||
};
|
||||
|
||||
class AlbumStudioRowConnector extends Component {
|
||||
|
@ -33,7 +35,7 @@ class AlbumStudioRowConnector extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onSeriesMonitoredPress = () => {
|
||||
onArtistMonitoredPress = () => {
|
||||
const {
|
||||
artistId,
|
||||
monitored
|
||||
|
@ -45,11 +47,10 @@ class AlbumStudioRowConnector extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
onSeasonMonitoredPress = (seasonNumber, monitored) => {
|
||||
this.props.toggleSeasonMonitored({
|
||||
artistId: this.props.artistId,
|
||||
seasonNumber,
|
||||
monitored
|
||||
onAlbumMonitoredPress = (episodeId, monitored) => {
|
||||
this.props.toggleEpisodeMonitored({
|
||||
episodeId,
|
||||
monitored: !monitored
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -60,8 +61,8 @@ class AlbumStudioRowConnector extends Component {
|
|||
return (
|
||||
<AlbumStudioRow
|
||||
{...this.props}
|
||||
onSeriesMonitoredPress={this.onSeriesMonitoredPress}
|
||||
onSeasonMonitoredPress={this.onSeasonMonitoredPress}
|
||||
onArtistMonitoredPress={this.onArtistMonitoredPress}
|
||||
onAlbumMonitoredPress={this.onAlbumMonitoredPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -71,7 +72,8 @@ AlbumStudioRowConnector.propTypes = {
|
|||
artistId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
toggleSeriesMonitored: PropTypes.func.isRequired,
|
||||
toggleSeasonMonitored: PropTypes.func.isRequired
|
||||
toggleSeasonMonitored: PropTypes.func.isRequired,
|
||||
toggleEpisodeMonitored: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioRowConnector);
|
||||
|
|
|
@ -13,7 +13,7 @@ import AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector'
|
|||
import ImportArtist from 'AddArtist/ImportArtist/ImportArtist';
|
||||
import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector';
|
||||
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
|
||||
import SeriesDetailsPageConnector from 'Artist/Details/SeriesDetailsPageConnector';
|
||||
import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector';
|
||||
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
||||
import HistoryConnector from 'Activity/History/HistoryConnector';
|
||||
import QueueConnector from 'Activity/Queue/QueueConnector';
|
||||
|
@ -93,7 +93,7 @@ function App({ store, history }) {
|
|||
|
||||
<Route
|
||||
path="/artist/:nameSlug"
|
||||
component={SeriesDetailsPageConnector}
|
||||
component={ArtistDetailsPageConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
|
|
|
@ -10,15 +10,6 @@
|
|||
width: 42px;
|
||||
}
|
||||
|
||||
.episodeNumber {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.language,
|
||||
.audio,
|
||||
.video,
|
||||
.status {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import Label from 'Components/Label';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||
import EpisodeSearchCellConnector from 'Episode/EpisodeSearchCellConnector';
|
||||
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
||||
|
||||
import styles from './AlbumRow.css';
|
||||
|
||||
function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) {
|
||||
if (episodeFileCount === episodeCount && episodeCount > 0) {
|
||||
return kinds.SUCCESS;
|
||||
}
|
||||
|
||||
if (!monitored) {
|
||||
return kinds.WARNING;
|
||||
}
|
||||
|
||||
return kinds.DANGER;
|
||||
}
|
||||
|
||||
class AlbumRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isDetailsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onManualSearchPress = () => {
|
||||
this.setState({ isDetailsModalOpen: true });
|
||||
}
|
||||
|
||||
onDetailsModalClose = () => {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
|
||||
onMonitorAlbumPress = (monitored, options) => {
|
||||
this.props.onMonitorAlbumPress(this.props.id, monitored, options);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
artistId,
|
||||
monitored,
|
||||
statistics,
|
||||
duration,
|
||||
releaseDate,
|
||||
title,
|
||||
isSaving,
|
||||
artistMonitored,
|
||||
path,
|
||||
columns
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
trackCount,
|
||||
trackFileCount,
|
||||
totalTrackCount
|
||||
} = statistics;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'monitored') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.monitored}
|
||||
>
|
||||
<MonitorToggleButton
|
||||
monitored={monitored}
|
||||
isDisabled={!artistMonitored}
|
||||
isSaving={isSaving}
|
||||
onPress={this.onMonitorAlbumPress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'title') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.title}
|
||||
>
|
||||
<EpisodeTitleLink
|
||||
episodeId={id}
|
||||
artistId={artistId}
|
||||
episodeTitle={title}
|
||||
showOpenSeriesButton={false}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'path') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
path
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'trackCount') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
statistics.trackCount
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'duration') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
formatTimeSpan(duration)
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'releaseDate') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
date={releaseDate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'status') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.status}
|
||||
>
|
||||
<Label
|
||||
title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`}
|
||||
kind={getEpisodeCountKind(monitored, trackFileCount, trackCount)}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
{
|
||||
<span>{trackFileCount} / {trackCount}</span>
|
||||
}
|
||||
</Label>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<EpisodeSearchCellConnector
|
||||
key={name}
|
||||
episodeId={id}
|
||||
artistId={artistId}
|
||||
episodeTitle={title}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
duration: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool,
|
||||
unverifiedSceneNumbering: PropTypes.bool,
|
||||
artistMonitored: PropTypes.bool.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
path: PropTypes.string,
|
||||
mediaInfo: PropTypes.object,
|
||||
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onMonitorAlbumPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AlbumRow;
|
|
@ -4,7 +4,7 @@ import { createSelector } from 'reselect';
|
|||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import createEpisodeFileSelector from 'Store/Selectors/createEpisodeFileSelector';
|
||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||
import EpisodeRow from './EpisodeRow';
|
||||
import AlbumRow from './AlbumRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
|
@ -17,7 +17,7 @@ function createMapStateToProps() {
|
|||
const alternateTitles = sceneSeasonNumber ? _.filter(series.alternateTitles, { sceneSeasonNumber }) : [];
|
||||
|
||||
return {
|
||||
seriesMonitored: series.monitored,
|
||||
artistMonitored: series.monitored,
|
||||
seriesType: series.seriesType,
|
||||
episodeFilePath: episodeFile ? episodeFile.path : null,
|
||||
episodeFileRelativePath: episodeFile ? episodeFile.relativePath : null,
|
||||
|
@ -26,4 +26,4 @@ function createMapStateToProps() {
|
|||
}
|
||||
);
|
||||
}
|
||||
export default connect(createMapStateToProps)(EpisodeRow);
|
||||
export default connect(createMapStateToProps)(AlbumRow);
|
|
@ -1,8 +1,8 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import styles from './SeriesAlternateTitles.css';
|
||||
import styles from './ArtistAlternateTitles.css';
|
||||
|
||||
function SeriesAlternateTitles({ alternateTitles }) {
|
||||
function ArtistAlternateTitles({ alternateTitles }) {
|
||||
return (
|
||||
<ul>
|
||||
{
|
||||
|
@ -21,8 +21,8 @@ function SeriesAlternateTitles({ alternateTitles }) {
|
|||
);
|
||||
}
|
||||
|
||||
SeriesAlternateTitles.propTypes = {
|
||||
ArtistAlternateTitles.propTypes = {
|
||||
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
|
||||
};
|
||||
|
||||
export default SeriesAlternateTitles;
|
||||
export default ArtistAlternateTitles;
|
|
@ -61,11 +61,11 @@
|
|||
line-height: 50px;
|
||||
}
|
||||
|
||||
.seriesNavigationButtons {
|
||||
.artistNavigationButtons {
|
||||
white-space: no-wrap;
|
||||
}
|
||||
|
||||
.seriesNavigationButton {
|
||||
.artistNavigationButton {
|
||||
composes: button from 'Components/Link/IconButton.css';
|
||||
|
||||
margin-left: 5px;
|
||||
|
@ -90,7 +90,6 @@
|
|||
|
||||
.sizeOnDisk,
|
||||
.qualityProfileName,
|
||||
.network,
|
||||
.links,
|
||||
.tags {
|
||||
margin-left: 8px;
|
|
@ -24,11 +24,29 @@ import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfil
|
|||
import ArtistPoster from 'Artist/ArtistPoster';
|
||||
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
|
||||
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
||||
import SeriesAlternateTitles from './SeriesAlternateTitles';
|
||||
import SeriesDetailsSeasonConnector from './SeriesDetailsSeasonConnector';
|
||||
import SeriesTagsConnector from './SeriesTagsConnector';
|
||||
import SeriesDetailsLinks from './SeriesDetailsLinks';
|
||||
import styles from './SeriesDetails.css';
|
||||
import ArtistAlternateTitles from './ArtistAlternateTitles';
|
||||
import ArtistDetailsSeasonConnector from './ArtistDetailsSeasonConnector';
|
||||
import ArtistTagsConnector from './ArtistTagsConnector';
|
||||
import ArtistDetailsLinks from './ArtistDetailsLinks';
|
||||
import styles from './ArtistDetails.css';
|
||||
|
||||
const albumTypes = [
|
||||
{
|
||||
name: 'album',
|
||||
label: 'Album',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'single',
|
||||
label: 'Single',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'ep',
|
||||
label: 'EP',
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
function getFanartUrl(images) {
|
||||
const fanartImage = _.find(images, { coverType: 'fanart' });
|
||||
|
@ -46,7 +64,7 @@ function getExpandedState(newState) {
|
|||
};
|
||||
}
|
||||
|
||||
class SeriesDetails extends Component {
|
||||
class ArtistDetails extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
@ -133,8 +151,6 @@ class SeriesDetails extends Component {
|
|||
const {
|
||||
id,
|
||||
foreignArtistId,
|
||||
tvMazeId,
|
||||
imdbId,
|
||||
artistName,
|
||||
ratings,
|
||||
sizeOnDisk,
|
||||
|
@ -142,7 +158,6 @@ class SeriesDetails extends Component {
|
|||
qualityProfileId,
|
||||
monitored,
|
||||
status,
|
||||
network,
|
||||
overview,
|
||||
images,
|
||||
albums,
|
||||
|
@ -154,8 +169,8 @@ class SeriesDetails extends Component {
|
|||
isPopulated,
|
||||
episodesError,
|
||||
episodeFilesError,
|
||||
previousSeries,
|
||||
nextSeries,
|
||||
previousArtist,
|
||||
nextArtist,
|
||||
onRefreshPress,
|
||||
onSearchPress
|
||||
} = this.props;
|
||||
|
@ -172,12 +187,12 @@ class SeriesDetails extends Component {
|
|||
|
||||
const continuing = status === 'continuing';
|
||||
|
||||
let episodeFilesCountMessage = 'No episode files';
|
||||
let episodeFilesCountMessage = 'No track files';
|
||||
|
||||
if (trackFileCount === 1) {
|
||||
episodeFilesCountMessage = '1 episode file';
|
||||
episodeFilesCountMessage = '1 track file';
|
||||
} else if (trackFileCount > 1) {
|
||||
episodeFilesCountMessage = `${trackFileCount} episode files`;
|
||||
episodeFilesCountMessage = `${trackFileCount} track files`;
|
||||
}
|
||||
|
||||
let expandIcon = icons.EXPAND_INDETERMINATE;
|
||||
|
@ -217,7 +232,7 @@ class SeriesDetails extends Component {
|
|||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Manage Episodes"
|
||||
label="Manage Tracks"
|
||||
iconName={icons.EPISODE_FILE}
|
||||
onPress={this.onManageEpisodesPress}
|
||||
/>
|
||||
|
@ -281,28 +296,28 @@ class SeriesDetails extends Component {
|
|||
/>
|
||||
}
|
||||
title="Alternate Titles"
|
||||
body={<SeriesAlternateTitles alternateTitles={alternateTitles} />}
|
||||
body={<ArtistAlternateTitles alternateTitles={alternateTitles} />}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.seriesNavigationButtons}>
|
||||
<div className={styles.artistNavigationButtons}>
|
||||
<IconButton
|
||||
className={styles.seriesNavigationButton}
|
||||
className={styles.artistNavigationButton}
|
||||
name={icons.ARROW_LEFT}
|
||||
size={30}
|
||||
title={`Go to ${previousSeries.artistName}`}
|
||||
to={`/artist/${previousSeries.nameSlug}`}
|
||||
title={`Go to ${previousArtist.artistName}`}
|
||||
to={`/artist/${previousArtist.nameSlug}`}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={styles.seriesNavigationButton}
|
||||
className={styles.artistNavigationButton}
|
||||
name={icons.ARROW_RIGHT}
|
||||
size={30}
|
||||
title={`Go to ${nextSeries.artistName}`}
|
||||
to={`/artist/${nextSeries.nameSlug}`}
|
||||
title={`Go to ${nextArtist.artistName}`}
|
||||
to={`/artist/${nextArtist.nameSlug}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -369,11 +384,11 @@ class SeriesDetails extends Component {
|
|||
|
||||
<Label
|
||||
className={styles.detailsLabel}
|
||||
title={continuing ? 'More episodes/another season is expected' : 'No additional episodes or or another season is expected'}
|
||||
title={continuing ? 'More albums are expected' : 'No additional albums are expected'}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<Icon
|
||||
name={continuing ? icons.SERIES_CONTINUING : icons.SERIES_ENDED}
|
||||
name={continuing ? icons.ARTIST_CONTINUING : icons.ARTIST_ENDED}
|
||||
size={17}
|
||||
/>
|
||||
|
||||
|
@ -382,24 +397,6 @@ class SeriesDetails extends Component {
|
|||
</span>
|
||||
</Label>
|
||||
|
||||
{
|
||||
!!network &&
|
||||
<Label
|
||||
className={styles.detailsLabel}
|
||||
title="Network"
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<Icon
|
||||
name={icons.NETWORK}
|
||||
size={17}
|
||||
/>
|
||||
|
||||
<span className={styles.qualityProfileName}>
|
||||
{network}
|
||||
</span>
|
||||
</Label>
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
anchor={
|
||||
<Label
|
||||
|
@ -417,10 +414,8 @@ class SeriesDetails extends Component {
|
|||
</Label>
|
||||
}
|
||||
tooltip={
|
||||
<SeriesDetailsLinks
|
||||
<ArtistDetailsLinks
|
||||
foreignArtistId={foreignArtistId}
|
||||
tvMazeId={tvMazeId}
|
||||
imdbId={imdbId}
|
||||
/>
|
||||
}
|
||||
kind={kinds.INVERSE}
|
||||
|
@ -445,7 +440,7 @@ class SeriesDetails extends Component {
|
|||
</span>
|
||||
</Label>
|
||||
}
|
||||
tooltip={<SeriesTagsConnector artistId={id} />}
|
||||
tooltip={<ArtistTagsConnector artistId={id} />}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
|
@ -477,18 +472,17 @@ class SeriesDetails extends Component {
|
|||
}
|
||||
|
||||
{
|
||||
isPopulated && !!albums.length &&
|
||||
isPopulated && !!albumTypes.length &&
|
||||
<div>
|
||||
{
|
||||
albums.slice(0).reverse().map((season) => {
|
||||
albumTypes.slice(0).map((season) => {
|
||||
return (
|
||||
<SeriesDetailsSeasonConnector
|
||||
key={season.id}
|
||||
<ArtistDetailsSeasonConnector
|
||||
key={season.name}
|
||||
artistId={id}
|
||||
albumId={season.id}
|
||||
statistics={season.statistics}
|
||||
label={season.label}
|
||||
{...season}
|
||||
isExpanded={expandedState[season.id]}
|
||||
isExpanded={expandedState[season.name]}
|
||||
onExpandPress={this.onExpandPress}
|
||||
/>
|
||||
);
|
||||
|
@ -536,11 +530,9 @@ class SeriesDetails extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
SeriesDetails.propTypes = {
|
||||
ArtistDetails.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
tvMazeId: PropTypes.number,
|
||||
imdbId: PropTypes.string,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
sizeOnDisk: PropTypes.number.isRequired,
|
||||
|
@ -548,7 +540,6 @@ SeriesDetails.propTypes = {
|
|||
qualityProfileId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
network: PropTypes.string,
|
||||
overview: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
albums: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
@ -560,10 +551,10 @@ SeriesDetails.propTypes = {
|
|||
isPopulated: PropTypes.bool.isRequired,
|
||||
episodesError: PropTypes.object,
|
||||
episodeFilesError: PropTypes.object,
|
||||
previousSeries: PropTypes.object.isRequired,
|
||||
nextSeries: PropTypes.object.isRequired,
|
||||
previousArtist: PropTypes.object.isRequired,
|
||||
nextArtist: PropTypes.object.isRequired,
|
||||
onRefreshPress: PropTypes.func.isRequired,
|
||||
onSearchPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SeriesDetails;
|
||||
export default ArtistDetails;
|
|
@ -11,7 +11,7 @@ import { fetchEpisodeFiles, clearEpisodeFiles } from 'Store/Actions/episodeFileA
|
|||
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import SeriesDetails from './SeriesDetails';
|
||||
import ArtistDetails from './ArtistDetails';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
|
@ -21,7 +21,7 @@ function createMapStateToProps() {
|
|||
createAllArtistSelector(),
|
||||
createCommandsSelector(),
|
||||
(nameSlug, episodes, episodeFiles, allSeries, commands) => {
|
||||
const sortedArtist = _.orderBy(allSeries, 'sortTitle');
|
||||
const sortedArtist = _.orderBy(allSeries, 'sortName');
|
||||
const seriesIndex = _.findIndex(sortedArtist, { nameSlug });
|
||||
const series = sortedArtist[seriesIndex];
|
||||
|
||||
|
@ -29,15 +29,15 @@ function createMapStateToProps() {
|
|||
return {};
|
||||
}
|
||||
|
||||
const previousSeries = sortedArtist[seriesIndex - 1] || _.last(sortedArtist);
|
||||
const nextSeries = sortedArtist[seriesIndex + 1] || _.first(sortedArtist);
|
||||
const isSeriesRefreshing = !!findCommand(commands, { name: commandNames.REFRESH_ARTIST, artistId: series.id });
|
||||
const allSeriesRefreshing = _.some(commands, (command) => command.name === commandNames.REFRESH_ARTIST && !command.body.artistId);
|
||||
const isRefreshing = isSeriesRefreshing || allSeriesRefreshing;
|
||||
const previousArtist = sortedArtist[seriesIndex - 1] || _.last(sortedArtist);
|
||||
const nextArtist = sortedArtist[seriesIndex + 1] || _.first(sortedArtist);
|
||||
const isArtistRefreshing = !!findCommand(commands, { name: commandNames.REFRESH_ARTIST, artistId: series.id });
|
||||
const allArtistRefreshing = _.some(commands, (command) => command.name === commandNames.REFRESH_ARTIST && !command.body.artistId);
|
||||
const isRefreshing = isArtistRefreshing || allArtistRefreshing;
|
||||
const isSearching = !!findCommand(commands, { name: commandNames.ARTIST_SEARCH, artistId: series.id });
|
||||
const isRenamingFiles = !!findCommand(commands, { name: commandNames.RENAME_FILES, artistId: series.id });
|
||||
const isRenamingSeriesCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST });
|
||||
const isRenamingSeries = !!(isRenamingSeriesCommand && isRenamingSeriesCommand.body.artistId.indexOf(series.id) > -1);
|
||||
const isRenamingArtistCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST });
|
||||
const isRenamingArtist = !!(isRenamingArtistCommand && isRenamingArtistCommand.body.artistId.indexOf(series.id) > -1);
|
||||
|
||||
const isFetching = episodes.isFetching || episodeFiles.isFetching;
|
||||
const isPopulated = episodes.isPopulated && episodeFiles.isPopulated;
|
||||
|
@ -58,13 +58,13 @@ function createMapStateToProps() {
|
|||
isRefreshing,
|
||||
isSearching,
|
||||
isRenamingFiles,
|
||||
isRenamingSeries,
|
||||
isRenamingArtist,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
episodesError,
|
||||
episodeFilesError,
|
||||
previousSeries,
|
||||
nextSeries
|
||||
previousArtist,
|
||||
nextArtist
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -80,7 +80,7 @@ const mapDispatchToProps = {
|
|||
executeCommand
|
||||
};
|
||||
|
||||
class SeriesDetailsConnector extends Component {
|
||||
class ArtistDetailsConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
@ -94,13 +94,13 @@ class SeriesDetailsConnector extends Component {
|
|||
id,
|
||||
isRefreshing,
|
||||
isRenamingFiles,
|
||||
isRenamingSeries
|
||||
isRenamingArtist
|
||||
} = this.props;
|
||||
|
||||
if (
|
||||
(prevProps.isRefreshing && !isRefreshing) ||
|
||||
(prevProps.isRenamingFiles && !isRenamingFiles) ||
|
||||
(prevProps.isRenamingSeries && !isRenamingSeries)
|
||||
(prevProps.isRenamingArtist && !isRenamingArtist)
|
||||
) {
|
||||
this._populate();
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ class SeriesDetailsConnector extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<SeriesDetails
|
||||
<ArtistDetails
|
||||
{...this.props}
|
||||
onRefreshPress={this.onRefreshPress}
|
||||
onSearchPress={this.onSearchPress}
|
||||
|
@ -166,12 +166,12 @@ class SeriesDetailsConnector extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
SeriesDetailsConnector.propTypes = {
|
||||
ArtistDetailsConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
nameSlug: PropTypes.string.isRequired,
|
||||
isRefreshing: PropTypes.bool.isRequired,
|
||||
isRenamingFiles: PropTypes.bool.isRequired,
|
||||
isRenamingSeries: PropTypes.bool.isRequired,
|
||||
isRenamingArtist: PropTypes.bool.isRequired,
|
||||
fetchEpisodes: PropTypes.func.isRequired,
|
||||
clearEpisodes: PropTypes.func.isRequired,
|
||||
fetchEpisodeFiles: PropTypes.func.isRequired,
|
||||
|
@ -181,4 +181,4 @@ SeriesDetailsConnector.propTypes = {
|
|||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(SeriesDetailsConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsConnector);
|
|
@ -0,0 +1,36 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import styles from './ArtistDetailsLinks.css';
|
||||
|
||||
function ArtistDetailsLinks(props) {
|
||||
const {
|
||||
foreignArtistId
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={styles.links}>
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`https://musicbrainz.org/artist/${foreignArtistId}`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Musicbrainz
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ArtistDetailsLinks.propTypes = {
|
||||
foreignArtistId: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default ArtistDetailsLinks;
|
|
@ -6,7 +6,7 @@ import { createSelector } from 'reselect';
|
|||
import { push } from 'react-router-redux';
|
||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
||||
import NotFound from 'Components/NotFound';
|
||||
import SeriesDetailsConnector from './SeriesDetailsConnector';
|
||||
import ArtistDetailsConnector from './ArtistDetailsConnector';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
|
@ -31,7 +31,7 @@ const mapDispatchToProps = {
|
|||
push
|
||||
};
|
||||
|
||||
class SeriesDetailsPageConnector extends Component {
|
||||
class ArtistDetailsPageConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
@ -60,17 +60,17 @@ class SeriesDetailsPageConnector extends Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<SeriesDetailsConnector
|
||||
<ArtistDetailsConnector
|
||||
nameSlug={nameSlug}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SeriesDetailsPageConnector.propTypes = {
|
||||
ArtistDetailsPageConnector.propTypes = {
|
||||
nameSlug: PropTypes.string,
|
||||
match: PropTypes.shape({ params: PropTypes.shape({ nameSlug: PropTypes.string.isRequired }).isRequired }).isRequired,
|
||||
push: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(SeriesDetailsPageConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsPageConnector);
|
|
@ -1,4 +1,4 @@
|
|||
.season {
|
||||
.albumType {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid $borderColor;
|
||||
border-radius: 4px;
|
||||
|
@ -17,11 +17,17 @@
|
|||
font-size: 24px;
|
||||
}
|
||||
|
||||
.seasonNumber {
|
||||
margin-right: 10px;
|
||||
.albumTypeLabel {
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.albumCount {
|
||||
font-size: 18px;
|
||||
font-style: italic;
|
||||
color: #8895aa;
|
||||
}
|
||||
|
||||
.episodeCountContainer {
|
||||
margin-left: 10px;
|
||||
vertical-align: text-bottom;
|
||||
|
@ -38,7 +44,7 @@
|
|||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 1 600px;
|
||||
flex: 0 1 300px;
|
||||
}
|
||||
|
||||
.left,
|
|
@ -7,9 +7,7 @@ import getToggledRange from 'Utilities/Table/getToggledRange';
|
|||
import { align, icons, kinds, sizes } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import Menu from 'Components/Menu/Menu';
|
||||
|
@ -20,22 +18,10 @@ import Table from 'Components/Table/Table';
|
|||
import TableBody from 'Components/Table/TableBody';
|
||||
import EpisodeFileEditorModal from 'EpisodeFile/Editor/EpisodeFileEditorModal';
|
||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||
import EpisodeRowConnector from './EpisodeRowConnector';
|
||||
import styles from './SeriesDetailsSeason.css';
|
||||
import AlbumRowConnector from './AlbumRowConnector';
|
||||
import styles from './ArtistDetailsSeason.css';
|
||||
|
||||
function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) {
|
||||
if (episodeFileCount === episodeCount && episodeCount > 0) {
|
||||
return kinds.SUCCESS;
|
||||
}
|
||||
|
||||
if (!monitored) {
|
||||
return kinds.WARNING;
|
||||
}
|
||||
|
||||
return kinds.DANGER;
|
||||
}
|
||||
|
||||
class SeriesDetailsSeason extends Component {
|
||||
class ArtistDetailsSeason extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
@ -65,7 +51,7 @@ class SeriesDetailsSeason extends Component {
|
|||
|
||||
_expandByDefault() {
|
||||
const {
|
||||
albumId,
|
||||
name,
|
||||
onExpandPress,
|
||||
items
|
||||
} = this.props;
|
||||
|
@ -75,7 +61,7 @@ class SeriesDetailsSeason extends Component {
|
|||
isAfter(item.airDateUtc, { days: -30 });
|
||||
});
|
||||
|
||||
onExpandPress(albumId, expand && albumId > 0);
|
||||
onExpandPress(name, expand && name > 0);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -99,29 +85,29 @@ class SeriesDetailsSeason extends Component {
|
|||
|
||||
onExpandPress = () => {
|
||||
const {
|
||||
albumId,
|
||||
name,
|
||||
isExpanded
|
||||
} = this.props;
|
||||
|
||||
this.props.onExpandPress(albumId, !isExpanded);
|
||||
this.props.onExpandPress(name, !isExpanded);
|
||||
}
|
||||
|
||||
onMonitorEpisodePress = (episodeId, monitored, { shiftKey }) => {
|
||||
onMonitorAlbumPress = (albumId, monitored, { shiftKey }) => {
|
||||
const lastToggled = this.state.lastToggledEpisode;
|
||||
const episodeIds = [episodeId];
|
||||
const albumIds = [albumId];
|
||||
|
||||
if (shiftKey && lastToggled) {
|
||||
const { lower, upper } = getToggledRange(this.props.items, episodeId, lastToggled);
|
||||
const { lower, upper } = getToggledRange(this.props.items, albumId, lastToggled);
|
||||
const items = this.props.items;
|
||||
|
||||
for (let i = lower; i < upper; i++) {
|
||||
episodeIds.push(items[i].id);
|
||||
albumIds.push(items[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ lastToggledEpisode: episodeId });
|
||||
this.setState({ lastToggledEpisode: albumId });
|
||||
|
||||
this.props.onMonitorEpisodePress(_.uniq(episodeIds), monitored);
|
||||
this.props.onMonitorAlbumPress(_.uniq(albumIds), monitored);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -130,29 +116,19 @@ class SeriesDetailsSeason extends Component {
|
|||
render() {
|
||||
const {
|
||||
artistId,
|
||||
monitored,
|
||||
title,
|
||||
releaseDate,
|
||||
albumId,
|
||||
statistics,
|
||||
label,
|
||||
items,
|
||||
columns,
|
||||
isSaving,
|
||||
isExpanded,
|
||||
isSearching,
|
||||
seriesMonitored,
|
||||
artistMonitored,
|
||||
isSmallScreen,
|
||||
onTableOptionChange,
|
||||
onMonitorSeasonPress,
|
||||
onSearchPress
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
trackCount,
|
||||
trackFileCount,
|
||||
totalTrackCount
|
||||
} = statistics;
|
||||
|
||||
const {
|
||||
isOrganizeModalOpen,
|
||||
isManageEpisodesOpen
|
||||
|
@ -160,37 +136,22 @@ class SeriesDetailsSeason extends Component {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={styles.season}
|
||||
className={styles.albumType}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.left}>
|
||||
<MonitorToggleButton
|
||||
monitored={monitored}
|
||||
isDisabled={!seriesMonitored}
|
||||
isSaving={isSaving}
|
||||
size={24}
|
||||
onPress={onMonitorSeasonPress}
|
||||
/>
|
||||
|
||||
{
|
||||
albumId === 0 ?
|
||||
<span className={styles.seasonNumber}>
|
||||
Specials
|
||||
</span> :
|
||||
<span className={styles.seasonNumber}>
|
||||
{title}
|
||||
<div>
|
||||
<span className={styles.albumTypeLabel}>
|
||||
{label}
|
||||
</span>
|
||||
|
||||
<span className={styles.albumCount}>
|
||||
({items.length} Releases)
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<Label
|
||||
title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`}
|
||||
kind={getEpisodeCountKind(monitored, trackFileCount, trackCount)}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{
|
||||
<span>{trackFileCount} / {trackCount}</span>
|
||||
}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
|
@ -198,6 +159,13 @@ class SeriesDetailsSeason extends Component {
|
|||
onPress={this.onExpandPress}
|
||||
>
|
||||
|
||||
<Icon
|
||||
className={styles.expandButtonIcon}
|
||||
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
|
||||
title={isExpanded ? 'Hide albums' : 'Show albums'}
|
||||
size={24}
|
||||
/>
|
||||
|
||||
{
|
||||
!isSmallScreen &&
|
||||
<span> </span>
|
||||
|
@ -266,37 +234,62 @@ class SeriesDetailsSeason extends Component {
|
|||
onPress={onSearchPress}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={styles.actionButton}
|
||||
name={icons.ORGANIZE}
|
||||
title="Preview rename for this album"
|
||||
size={24}
|
||||
onPress={this.onOrganizePress}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={styles.actionButton}
|
||||
name={icons.EPISODE_FILE}
|
||||
title="Manage track files in this artist"
|
||||
size={24}
|
||||
onPress={this.onManageEpisodesPress}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
isExpanded &&
|
||||
<div className={styles.episodes}>
|
||||
{
|
||||
items.length ?
|
||||
<Table
|
||||
columns={columns}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<AlbumRowConnector
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
{...item}
|
||||
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table> :
|
||||
|
||||
<div className={styles.noEpisodes}>
|
||||
No albums in this group
|
||||
</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}
|
||||
albumId={albumId}
|
||||
onModalClose={this.onOrganizeModalClose}
|
||||
/>
|
||||
|
||||
<EpisodeFileEditorModal
|
||||
isOpen={isManageEpisodesOpen}
|
||||
artistId={artistId}
|
||||
albumId={albumId}
|
||||
onModalClose={this.onManageEpisodesModalClose}
|
||||
/>
|
||||
</div>
|
||||
|
@ -304,33 +297,22 @@ class SeriesDetailsSeason extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
SeriesDetailsSeason.propTypes = {
|
||||
ArtistDetailsSeason.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isSaving: PropTypes.bool,
|
||||
isExpanded: PropTypes.bool,
|
||||
isSearching: PropTypes.bool.isRequired,
|
||||
seriesMonitored: PropTypes.bool.isRequired,
|
||||
artistMonitored: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
onTableOptionChange: PropTypes.func.isRequired,
|
||||
onMonitorSeasonPress: PropTypes.func.isRequired,
|
||||
onExpandPress: PropTypes.func.isRequired,
|
||||
onMonitorEpisodePress: PropTypes.func.isRequired,
|
||||
onMonitorAlbumPress: PropTypes.func.isRequired,
|
||||
onSearchPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
SeriesDetailsSeason.defaultProps = {
|
||||
statistics: {
|
||||
trackFileCount: 0,
|
||||
totalTrackCount: 0,
|
||||
percentOfTracks: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default SeriesDetailsSeason;
|
||||
export default ArtistDetailsSeason;
|
|
@ -11,30 +11,30 @@ import { toggleSeasonMonitored } from 'Store/Actions/artistActions';
|
|||
import { toggleEpisodesMonitored, setEpisodesTableOption } from 'Store/Actions/episodeActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import SeriesDetailsSeason from './SeriesDetailsSeason';
|
||||
import ArtistDetailsSeason from './ArtistDetailsSeason';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { seasonNumber }) => seasonNumber,
|
||||
(state, { label }) => label,
|
||||
(state) => state.episodes,
|
||||
createArtistSelector(),
|
||||
createCommandsSelector(),
|
||||
createDimensionsSelector(),
|
||||
(seasonNumber, episodes, series, commands, dimensions) => {
|
||||
(label, episodes, series, commands, dimensions) => {
|
||||
const isSearching = !!findCommand(commands, {
|
||||
name: commandNames.SEASON_SEARCH,
|
||||
artistId: series.id,
|
||||
seasonNumber
|
||||
label
|
||||
});
|
||||
|
||||
const episodesInSeason = _.filter(episodes.items, { seasonNumber });
|
||||
const sortedEpisodes = _.orderBy(episodesInSeason, 'episodeNumber', 'desc');
|
||||
const episodesInSeason = _.filter(episodes.items, { albumType: label });
|
||||
const sortedEpisodes = _.orderBy(episodesInSeason, 'releaseDate', 'desc');
|
||||
|
||||
return {
|
||||
items: sortedEpisodes,
|
||||
columns: episodes.columns,
|
||||
isSearching,
|
||||
seriesMonitored: series.monitored,
|
||||
artistMonitored: series.monitored,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
};
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ const mapDispatchToProps = {
|
|||
executeCommand
|
||||
};
|
||||
|
||||
class SeriesDetailsSeasonConnector extends Component {
|
||||
class ArtistDetailsSeasonConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
@ -59,33 +59,29 @@ class SeriesDetailsSeasonConnector extends Component {
|
|||
|
||||
onMonitorSeasonPress = (monitored) => {
|
||||
const {
|
||||
artistId,
|
||||
albumId
|
||||
artistId
|
||||
} = this.props;
|
||||
|
||||
this.props.toggleSeasonMonitored({
|
||||
artistId,
|
||||
albumId,
|
||||
monitored
|
||||
});
|
||||
}
|
||||
|
||||
onSearchPress = () => {
|
||||
const {
|
||||
artistId,
|
||||
albumId
|
||||
artistId
|
||||
} = this.props;
|
||||
|
||||
this.props.executeCommand({
|
||||
name: commandNames.SEASON_SEARCH,
|
||||
artistId,
|
||||
albumIds: [albumId]
|
||||
artistId
|
||||
});
|
||||
}
|
||||
|
||||
onMonitorEpisodePress = (episodeIds, monitored) => {
|
||||
onMonitorAlbumPress = (albumIds, monitored) => {
|
||||
this.props.toggleEpisodesMonitored({
|
||||
episodeIds,
|
||||
albumIds,
|
||||
monitored
|
||||
});
|
||||
}
|
||||
|
@ -95,24 +91,23 @@ class SeriesDetailsSeasonConnector extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<SeriesDetailsSeason
|
||||
<ArtistDetailsSeason
|
||||
{...this.props}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onMonitorSeasonPress={this.onMonitorSeasonPress}
|
||||
onSearchPress={this.onSearchPress}
|
||||
onMonitorEpisodePress={this.onMonitorEpisodePress}
|
||||
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SeriesDetailsSeasonConnector.propTypes = {
|
||||
ArtistDetailsSeasonConnector.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
toggleSeasonMonitored: PropTypes.func.isRequired,
|
||||
toggleEpisodesMonitored: PropTypes.func.isRequired,
|
||||
setEpisodesTableOption: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(SeriesDetailsSeasonConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsSeasonConnector);
|
|
@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import Label from 'Components/Label';
|
||||
import styles from './SeriesTags.css';
|
||||
import styles from './ArtistTags.css';
|
||||
|
||||
function SeriesTags({ tags }) {
|
||||
function ArtistTags({ tags }) {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
|
@ -24,8 +24,8 @@ function SeriesTags({ tags }) {
|
|||
);
|
||||
}
|
||||
|
||||
SeriesTags.propTypes = {
|
||||
ArtistTags.propTypes = {
|
||||
tags: PropTypes.arrayOf(PropTypes.string).isRequired
|
||||
};
|
||||
|
||||
export default SeriesTags;
|
||||
export default ArtistTags;
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
|||
import { createSelector } from 'reselect';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||
import SeriesTags from './SeriesTags';
|
||||
import ArtistTags from './ArtistTags';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
|
@ -27,4 +27,4 @@ function createMapStateToProps() {
|
|||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(SeriesTags);
|
||||
export default connect(createMapStateToProps)(ArtistTags);
|
|
@ -1,266 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import EpisodeSearchCellConnector from 'Episode/EpisodeSearchCellConnector';
|
||||
import EpisodeNumber from 'Episode/EpisodeNumber';
|
||||
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
||||
import EpisodeStatusConnector from 'Episode/EpisodeStatusConnector';
|
||||
import EpisodeFileLanguageConnector from 'EpisodeFile/EpisodeFileLanguageConnector';
|
||||
import MediaInfoConnector from 'EpisodeFile/MediaInfoConnector';
|
||||
import * as mediaInfoTypes from 'EpisodeFile/mediaInfoTypes';
|
||||
|
||||
import styles from './EpisodeRow.css';
|
||||
|
||||
class EpisodeRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isDetailsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onManualSearchPress = () => {
|
||||
this.setState({ isDetailsModalOpen: true });
|
||||
}
|
||||
|
||||
onDetailsModalClose = () => {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
|
||||
onMonitorEpisodePress = (monitored, options) => {
|
||||
this.props.onMonitorEpisodePress(this.props.id, monitored, options);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
artistId,
|
||||
episodeFileId,
|
||||
monitored,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
absoluteEpisodeNumber,
|
||||
sceneSeasonNumber,
|
||||
sceneEpisodeNumber,
|
||||
sceneAbsoluteEpisodeNumber,
|
||||
airDateUtc,
|
||||
title,
|
||||
unverifiedSceneNumbering,
|
||||
isSaving,
|
||||
seriesMonitored,
|
||||
seriesType,
|
||||
episodeFilePath,
|
||||
episodeFileRelativePath,
|
||||
alternateTitles,
|
||||
columns
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'monitored') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.monitored}
|
||||
>
|
||||
<MonitorToggleButton
|
||||
monitored={monitored}
|
||||
isDisabled={!seriesMonitored}
|
||||
isSaving={isSaving}
|
||||
onPress={this.onMonitorEpisodePress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'episodeNumber') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.episodeNumber}
|
||||
>
|
||||
<EpisodeNumber
|
||||
seasonNumber={seasonNumber}
|
||||
episodeNumber={episodeNumber}
|
||||
absoluteEpisodeNumber={absoluteEpisodeNumber}
|
||||
unverifiedSceneNumbering={unverifiedSceneNumbering}
|
||||
seriesType={seriesType}
|
||||
sceneSeasonNumber={sceneSeasonNumber}
|
||||
sceneEpisodeNumber={sceneEpisodeNumber}
|
||||
sceneAbsoluteEpisodeNumber={sceneAbsoluteEpisodeNumber}
|
||||
alternateTitles={alternateTitles}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'title') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.title}
|
||||
>
|
||||
<EpisodeTitleLink
|
||||
episodeId={id}
|
||||
artistId={artistId}
|
||||
episodeTitle={title}
|
||||
showOpenSeriesButton={false}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'path') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
episodeFilePath
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'relativePath') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
episodeFileRelativePath
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'airDateUtc') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
date={airDateUtc}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'language') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.language}
|
||||
>
|
||||
<EpisodeFileLanguageConnector
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'audioInfo') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.audio}
|
||||
>
|
||||
<MediaInfoConnector
|
||||
type={mediaInfoTypes.AUDIO}
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'videoCodec') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.video}
|
||||
>
|
||||
<MediaInfoConnector
|
||||
type={mediaInfoTypes.VIDEO}
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'status') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.status}
|
||||
>
|
||||
<EpisodeStatusConnector
|
||||
episodeId={id}
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<EpisodeSearchCellConnector
|
||||
key={name}
|
||||
episodeId={id}
|
||||
artistId={artistId}
|
||||
episodeTitle={title}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EpisodeRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
episodeFileId: PropTypes.number,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
episodeNumber: PropTypes.number.isRequired,
|
||||
absoluteEpisodeNumber: PropTypes.number,
|
||||
sceneSeasonNumber: PropTypes.number,
|
||||
sceneEpisodeNumber: PropTypes.number,
|
||||
sceneAbsoluteEpisodeNumber: PropTypes.number,
|
||||
airDateUtc: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool,
|
||||
unverifiedSceneNumbering: PropTypes.bool,
|
||||
seriesMonitored: PropTypes.bool.isRequired,
|
||||
seriesType: PropTypes.string.isRequired,
|
||||
episodeFilePath: PropTypes.string,
|
||||
episodeFileRelativePath: PropTypes.string,
|
||||
mediaInfo: PropTypes.object,
|
||||
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onMonitorEpisodePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default EpisodeRow;
|
|
@ -1,70 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import styles from './SeriesDetailsLinks.css';
|
||||
|
||||
function SeriesDetailsLinks(props) {
|
||||
const {
|
||||
foreignArtistId,
|
||||
tvMazeId,
|
||||
imdbId
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={styles.links}>
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`https://musicbrainz.org/artist/${foreignArtistId}`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Musicbrainz
|
||||
</Label>
|
||||
</Link>
|
||||
{
|
||||
!!tvMazeId &&
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`http://www.tvmaze.com/shows/${tvMazeId}/_`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
TV Maze
|
||||
</Label>
|
||||
</Link>
|
||||
}
|
||||
|
||||
{
|
||||
!!imdbId &&
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`http://imdb.com/title/${imdbId}/`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
IMDB
|
||||
</Label>
|
||||
</Link>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SeriesDetailsLinks.propTypes = {
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
tvMazeId: PropTypes.number,
|
||||
imdbId: PropTypes.string
|
||||
};
|
||||
|
||||
export default SeriesDetailsLinks;
|
|
@ -27,7 +27,7 @@ function ArtistStatusCell(props) {
|
|||
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={status === 'ended' ? icons.SERIES_ENDED : icons.SERIES_CONTINUING}
|
||||
name={status === 'ended' ? icons.ARTIST_ENDED : icons.ARTIST_CONTINUING}
|
||||
title={status === 'ended' ? 'Ended' : 'Continuing'}
|
||||
|
||||
/>
|
||||
|
|
|
@ -7,9 +7,9 @@ export const CUTOFF_UNMET_EPISODE_SEARCH = 'CutoffUnmetEpisodeSearch';
|
|||
export const DELETE_LOG_FILES = 'DeleteLogFiles';
|
||||
export const DELETE_UPDATE_LOG_FILES = 'DeleteUpdateLogFiles';
|
||||
export const DOWNLOADED_EPSIODES_SCAN = 'DownloadedEpisodesScan';
|
||||
export const EPISODE_SEARCH = 'EpisodeSearch';
|
||||
export const EPISODE_SEARCH = 'AlbumSearch';
|
||||
export const INTERACTIVE_IMPORT = 'ManualImport';
|
||||
export const MISSING_EPISODE_SEARCH = 'MissingEpisodeSearch';
|
||||
export const MISSING_ALBUM_SEARCH = 'MissingAlbumSearch';
|
||||
export const REFRESH_ARTIST = 'RefreshArtist';
|
||||
export const RENAME_FILES = 'RenameFiles';
|
||||
export const RENAME_ARTIST = 'RenameArtist';
|
||||
|
|
|
@ -19,7 +19,7 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
|
|||
|
||||
const links = [
|
||||
{
|
||||
iconName: icons.SERIES_CONTINUING,
|
||||
iconName: icons.ARTIST_CONTINUING,
|
||||
title: 'Artist',
|
||||
to: '/',
|
||||
alias: '/series',
|
||||
|
|
|
@ -11,7 +11,6 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
|
|||
import EpisodeSummaryConnector from './Summary/EpisodeSummaryConnector';
|
||||
import EpisodeHistoryConnector from './History/EpisodeHistoryConnector';
|
||||
import EpisodeSearchConnector from './Search/EpisodeSearchConnector';
|
||||
import SeasonEpisodeNumber from './SeasonEpisodeNumber';
|
||||
import styles from './EpisodeDetailsModalContent.css';
|
||||
|
||||
const tabs = [
|
||||
|
@ -47,26 +46,22 @@ class EpisodeDetailsModalContent extends Component {
|
|||
const {
|
||||
episodeId,
|
||||
episodeEntity,
|
||||
episodeFileId,
|
||||
artistId,
|
||||
seriesTitle,
|
||||
titleSlug,
|
||||
seriesMonitored,
|
||||
seriesType,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
absoluteEpisodeNumber,
|
||||
nameSlug,
|
||||
albumLabel,
|
||||
artistMonitored,
|
||||
episodeTitle,
|
||||
airDate,
|
||||
releaseDate,
|
||||
monitored,
|
||||
isSaving,
|
||||
showOpenSeriesButton,
|
||||
startInteractiveSearch,
|
||||
onMonitorEpisodePress,
|
||||
onMonitorAlbumPress,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const seriesLink = `/artist/${titleSlug}`;
|
||||
const seriesLink = `/artist/${nameSlug}`;
|
||||
|
||||
return (
|
||||
<ModalContent
|
||||
|
@ -78,9 +73,9 @@ class EpisodeDetailsModalContent extends Component {
|
|||
id={episodeId}
|
||||
monitored={monitored}
|
||||
size={18}
|
||||
isDisabled={!seriesMonitored}
|
||||
isDisabled={!artistMonitored}
|
||||
isSaving={isSaving}
|
||||
onPress={onMonitorEpisodePress}
|
||||
onPress={onMonitorAlbumPress}
|
||||
/>
|
||||
|
||||
<span className={styles.seriesTitle}>
|
||||
|
@ -89,16 +84,6 @@ class EpisodeDetailsModalContent extends Component {
|
|||
|
||||
<span className={styles.separator}>-</span>
|
||||
|
||||
<SeasonEpisodeNumber
|
||||
seasonNumber={seasonNumber}
|
||||
episodeNumber={episodeNumber}
|
||||
absoluteEpisodeNumber={absoluteEpisodeNumber}
|
||||
airDate={airDate}
|
||||
seriesType={seriesType}
|
||||
/>
|
||||
|
||||
<span className={styles.separator}>-</span>
|
||||
|
||||
{episodeTitle}
|
||||
</ModalHeader>
|
||||
|
||||
|
@ -137,7 +122,8 @@ class EpisodeDetailsModalContent extends Component {
|
|||
<EpisodeSummaryConnector
|
||||
episodeId={episodeId}
|
||||
episodeEntity={episodeEntity}
|
||||
episodeFileId={episodeFileId}
|
||||
releaseDate={releaseDate}
|
||||
albumLabel={albumLabel}
|
||||
artistId={artistId}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
@ -150,7 +136,7 @@ class EpisodeDetailsModalContent extends Component {
|
|||
|
||||
<TabPanel className={styles.tabPanel}>
|
||||
<EpisodeSearchConnector
|
||||
episodeId={episodeId}
|
||||
albumId={episodeId}
|
||||
startInteractiveSearch={startInteractiveSearch}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
|
@ -166,7 +152,7 @@ class EpisodeDetailsModalContent extends Component {
|
|||
to={seriesLink}
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Open Series
|
||||
Open Artist
|
||||
</Button>
|
||||
}
|
||||
|
||||
|
@ -184,28 +170,25 @@ class EpisodeDetailsModalContent extends Component {
|
|||
EpisodeDetailsModalContent.propTypes = {
|
||||
episodeId: PropTypes.number.isRequired,
|
||||
episodeEntity: PropTypes.string.isRequired,
|
||||
episodeFileId: PropTypes.number,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
seriesTitle: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
seriesMonitored: PropTypes.bool.isRequired,
|
||||
seriesType: PropTypes.string.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
episodeNumber: PropTypes.number.isRequired,
|
||||
absoluteEpisodeNumber: PropTypes.number,
|
||||
airDate: PropTypes.string.isRequired,
|
||||
nameSlug: PropTypes.string.isRequired,
|
||||
artistMonitored: PropTypes.bool.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
albumLabel: PropTypes.string.isRequired,
|
||||
episodeTitle: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
isSaving: PropTypes.bool,
|
||||
showOpenSeriesButton: PropTypes.bool,
|
||||
selectedTab: PropTypes.string.isRequired,
|
||||
startInteractiveSearch: PropTypes.bool.isRequired,
|
||||
onMonitorEpisodePress: PropTypes.func.isRequired,
|
||||
onMonitorAlbumPress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
EpisodeDetailsModalContent.defaultProps = {
|
||||
selectedTab: 'details',
|
||||
albumLabel: 'Unknown',
|
||||
episodeEntity: episodeEntities.EPISODES,
|
||||
startInteractiveSearch: false
|
||||
};
|
||||
|
|
|
@ -15,16 +15,16 @@ function createMapStateToProps() {
|
|||
createArtistSelector(),
|
||||
(episode, series) => {
|
||||
const {
|
||||
title: seriesTitle,
|
||||
titleSlug,
|
||||
monitored: seriesMonitored,
|
||||
artistName: seriesTitle,
|
||||
nameSlug,
|
||||
monitored: artistMonitored,
|
||||
seriesType
|
||||
} = series;
|
||||
|
||||
return {
|
||||
seriesTitle,
|
||||
titleSlug,
|
||||
seriesMonitored,
|
||||
nameSlug,
|
||||
artistMonitored,
|
||||
seriesType,
|
||||
...episode
|
||||
};
|
||||
|
@ -52,7 +52,7 @@ class EpisodeDetailsModalContentConnector extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onMonitorEpisodePress = (monitored) => {
|
||||
onMonitorAlbumPress = (monitored) => {
|
||||
const {
|
||||
episodeId,
|
||||
episodeEntity
|
||||
|
@ -72,7 +72,7 @@ class EpisodeDetailsModalContentConnector extends Component {
|
|||
return (
|
||||
<EpisodeDetailsModalContent
|
||||
{...this.props}
|
||||
onMonitorEpisodePress={this.onMonitorEpisodePress}
|
||||
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ function createMapStateToProps() {
|
|||
return false;
|
||||
}
|
||||
|
||||
return command.body.episodeIds.indexOf(episodeId) > -1;
|
||||
return command.body.albumIds.indexOf(episodeId) > -1;
|
||||
});
|
||||
|
||||
return {
|
||||
seriesMonitored: series.monitored,
|
||||
artistMonitored: series.monitored,
|
||||
seriesType: series.seriesType,
|
||||
isSearching
|
||||
};
|
||||
|
@ -38,7 +38,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||
onSearchPress(name, path) {
|
||||
dispatch(executeCommand({
|
||||
name: commandNames.EPISODE_SEARCH,
|
||||
episodeIds: [props.episodeId]
|
||||
albumIds: [props.episodeId]
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -61,13 +61,13 @@ class EpisodeHistory extends Component {
|
|||
|
||||
if (!isFetching && !!error) {
|
||||
return (
|
||||
<div>Unable to load episode history.</div>
|
||||
<div>Unable to load album history.</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isPopulated && !hasItems && !error) {
|
||||
return (
|
||||
<div>No episode history.</div>
|
||||
<div>No album history.</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class EpisodeSearchConnector extends Component {
|
|||
onQuickSearchPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.EPISODE_SEARCH,
|
||||
episodeIds: [this.props.episodeId]
|
||||
albumIds: [this.props.albumId]
|
||||
});
|
||||
|
||||
this.props.onModalClose();
|
||||
|
@ -80,7 +80,7 @@ class EpisodeSearchConnector extends Component {
|
|||
}
|
||||
|
||||
EpisodeSearchConnector.propTypes = {
|
||||
episodeId: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
startInteractiveSearch: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
|
|
|
@ -34,7 +34,7 @@ class InteractiveEpisodeSearchConnector extends Component {
|
|||
|
||||
componentDidMount() {
|
||||
const {
|
||||
episodeId,
|
||||
albumId,
|
||||
isPopulated
|
||||
} = this.props;
|
||||
|
||||
|
@ -43,7 +43,7 @@ class InteractiveEpisodeSearchConnector extends Component {
|
|||
|
||||
if (!isPopulated) {
|
||||
this.props.fetchReleases({
|
||||
episodeId
|
||||
albumId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ class InteractiveEpisodeSearchConnector extends Component {
|
|||
}
|
||||
|
||||
InteractiveEpisodeSearchConnector.propTypes = {
|
||||
episodeId: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
fetchReleases: PropTypes.func.isRequired,
|
||||
setReleasesSort: PropTypes.func.isRequired,
|
||||
|
|
|
@ -10,8 +10,8 @@ import Label from 'Components/Label';
|
|||
|
||||
function EpisodeAiring(props) {
|
||||
const {
|
||||
airDateUtc,
|
||||
network,
|
||||
releaseDate,
|
||||
albumLabel,
|
||||
shortDateFormat,
|
||||
showRelativeDates,
|
||||
timeFormat
|
||||
|
@ -22,11 +22,11 @@ function EpisodeAiring(props) {
|
|||
kind={kinds.INFO}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
{network}
|
||||
{albumLabel}
|
||||
</Label>
|
||||
);
|
||||
|
||||
if (!airDateUtc) {
|
||||
if (!releaseDate) {
|
||||
return (
|
||||
<span>
|
||||
TBA on {networkLabel}
|
||||
|
@ -34,50 +34,48 @@ function EpisodeAiring(props) {
|
|||
);
|
||||
}
|
||||
|
||||
const time = formatTime(airDateUtc, timeFormat);
|
||||
|
||||
if (!showRelativeDates) {
|
||||
return (
|
||||
<span>
|
||||
{moment(airDateUtc).format(shortDateFormat)} at {time} on {networkLabel}
|
||||
{moment(releaseDate).format(shortDateFormat)} on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isToday(airDateUtc)) {
|
||||
if (isToday(releaseDate)) {
|
||||
return (
|
||||
<span>
|
||||
{time} on {networkLabel}
|
||||
Today on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isTomorrow(airDateUtc)) {
|
||||
if (isTomorrow(releaseDate)) {
|
||||
return (
|
||||
<span>
|
||||
Tomorrow at {time} on {networkLabel}
|
||||
Tomorrow on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isInNextWeek(airDateUtc)) {
|
||||
if (isInNextWeek(releaseDate)) {
|
||||
return (
|
||||
<span>
|
||||
{moment(airDateUtc).format('dddd')} at {time} on {networkLabel}
|
||||
{moment(releaseDate).format('dddd')} on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{moment(airDateUtc).format(shortDateFormat)} at {time} on {networkLabel}
|
||||
{moment(releaseDate).format(shortDateFormat)} on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
EpisodeAiring.propTypes = {
|
||||
airDateUtc: PropTypes.string.isRequired,
|
||||
network: PropTypes.string.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
albumLabel: PropTypes.string.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
|
|
|
@ -45,9 +45,9 @@ class EpisodeSummary extends Component {
|
|||
render() {
|
||||
const {
|
||||
qualityProfileId,
|
||||
network,
|
||||
overview,
|
||||
airDateUtc,
|
||||
releaseDate,
|
||||
albumLabel,
|
||||
path,
|
||||
size,
|
||||
quality,
|
||||
|
@ -59,11 +59,11 @@ class EpisodeSummary extends Component {
|
|||
return (
|
||||
<div>
|
||||
<div>
|
||||
<span className={styles.infoTitle}>Airs</span>
|
||||
<span className={styles.infoTitle}>Releases</span>
|
||||
|
||||
<EpisodeAiringConnector
|
||||
airDateUtc={airDateUtc}
|
||||
network={network}
|
||||
releaseDate={releaseDate}
|
||||
albumLabel={albumLabel}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -84,7 +84,7 @@ class EpisodeSummary extends Component {
|
|||
{
|
||||
hasOverview ?
|
||||
overview :
|
||||
'No episode overview.'
|
||||
'No album overview.'
|
||||
}
|
||||
</div>
|
||||
|
||||
|
@ -151,11 +151,10 @@ class EpisodeSummary extends Component {
|
|||
}
|
||||
|
||||
EpisodeSummary.propTypes = {
|
||||
episodeFileId: PropTypes.number.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
network: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string,
|
||||
airDateUtc: PropTypes.string.isRequired,
|
||||
albumLabel: PropTypes.string,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
quality: PropTypes.object,
|
||||
|
|
|
@ -67,8 +67,8 @@ export const RSS = 'fa fa-rss';
|
|||
export const SAVE = 'fa fa-floppy-o';
|
||||
export const SCHEDULED = 'fa fa-clock-o';
|
||||
export const SEARCH = 'fa fa-search';
|
||||
export const SERIES_CONTINUING = 'fa fa-play';
|
||||
export const SERIES_ENDED = 'fa fa-stop';
|
||||
export const ARTIST_CONTINUING = 'fa fa-play';
|
||||
export const ARTIST_ENDED = 'fa fa-stop';
|
||||
export const SETTINGS = 'fa fa-cogs';
|
||||
export const SHUTDOWN = 'fa fa-power-off';
|
||||
export const SORT = 'fa fa-sort';
|
||||
|
|
|
@ -9,7 +9,7 @@ import { updateItem } from './baseActions';
|
|||
const section = 'episodes';
|
||||
|
||||
const episodeActionHandlers = {
|
||||
[types.FETCH_EPISODES]: createFetchHandler(section, '/track'),
|
||||
[types.FETCH_EPISODES]: createFetchHandler(section, '/album'),
|
||||
|
||||
[types.TOGGLE_EPISODE_MONITORED]: function(payload) {
|
||||
return function(dispatch, getState) {
|
||||
|
@ -28,7 +28,7 @@ const episodeActionHandlers = {
|
|||
}));
|
||||
|
||||
const promise = $.ajax({
|
||||
url: `/episode/${id}`,
|
||||
url: `/album/${id}`,
|
||||
method: 'PUT',
|
||||
data: JSON.stringify({ monitored }),
|
||||
dataType: 'json'
|
||||
|
@ -56,7 +56,7 @@ const episodeActionHandlers = {
|
|||
[types.TOGGLE_EPISODES_MONITORED]: function(payload) {
|
||||
return function(dispatch, getState) {
|
||||
const {
|
||||
episodeIds,
|
||||
albumIds,
|
||||
episodeEntity = episodeEntities.EPISODES,
|
||||
monitored
|
||||
} = payload;
|
||||
|
@ -64,7 +64,7 @@ const episodeActionHandlers = {
|
|||
const episodeSection = _.last(episodeEntity.split('.'));
|
||||
|
||||
dispatch(batchActions(
|
||||
episodeIds.map((episodeId) => {
|
||||
albumIds.map((episodeId) => {
|
||||
return updateItem({
|
||||
id: episodeId,
|
||||
section: episodeSection,
|
||||
|
@ -74,15 +74,15 @@ const episodeActionHandlers = {
|
|||
));
|
||||
|
||||
const promise = $.ajax({
|
||||
url: '/episode/monitor',
|
||||
url: '/album/monitor',
|
||||
method: 'PUT',
|
||||
data: JSON.stringify({ episodeIds, monitored }),
|
||||
data: JSON.stringify({ albumIds, monitored }),
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions(
|
||||
episodeIds.map((episodeId) => {
|
||||
albumIds.map((episodeId) => {
|
||||
return updateItem({
|
||||
id: episodeId,
|
||||
section: episodeSection,
|
||||
|
@ -95,7 +95,7 @@ const episodeActionHandlers = {
|
|||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(batchActions(
|
||||
episodeIds.map((episodeId) => {
|
||||
albumIds.map((episodeId) => {
|
||||
return updateItem({
|
||||
id: episodeId,
|
||||
section: episodeSection,
|
||||
|
|
|
@ -11,7 +11,7 @@ export const defaultState = {
|
|||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
sortKey: 'episodeNumber',
|
||||
sortKey: 'releaseDate',
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
items: [],
|
||||
|
||||
|
@ -22,11 +22,6 @@ export const defaultState = {
|
|||
isVisible: true,
|
||||
isModifiable: false
|
||||
},
|
||||
{
|
||||
name: 'episodeNumber',
|
||||
label: '#',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
|
@ -38,28 +33,18 @@ export const defaultState = {
|
|||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'relativePath',
|
||||
label: 'Relative Path',
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'airDateUtc',
|
||||
label: 'Air Date',
|
||||
name: 'releaseDate',
|
||||
label: 'Release Date',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'language',
|
||||
label: 'Language',
|
||||
name: 'trackCount',
|
||||
label: 'Track Count',
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'audioInfo',
|
||||
label: 'Audio Info',
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'videoCodec',
|
||||
label: 'Video Codec',
|
||||
name: 'duration',
|
||||
label: 'Duration',
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
|
|
|
@ -110,7 +110,7 @@ class CutoffUnmetConnector extends Component {
|
|||
onSearchSelectedPress = (selected) => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.EPISODE_SEARCH,
|
||||
episodeIds: selected
|
||||
albumIds: selected
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ function createMapStateToProps() {
|
|||
createCommandsSelector(),
|
||||
(missing, commands) => {
|
||||
const isSearchingForAlbums = _.some(commands, { name: commandNames.EPISODE_SEARCH });
|
||||
const isSearchingForMissingAlbums = _.some(commands, { name: commandNames.MISSING_EPISODE_SEARCH });
|
||||
const isSearchingForMissingAlbums = _.some(commands, { name: commandNames.MISSING_ALBUM_SEARCH });
|
||||
|
||||
return {
|
||||
isSearchingForAlbums,
|
||||
|
@ -100,7 +100,7 @@ class MissingConnector extends Component {
|
|||
onSearchSelectedPress = (selected) => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.EPISODE_SEARCH,
|
||||
episodeIds: selected
|
||||
albumIds: selected
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ class MissingConnector extends Component {
|
|||
|
||||
onSearchAllMissingPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.MISSING_EPISODE_SEARCH
|
||||
name: commandNames.MISSING_ALBUM_SEARCH
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue