[UI Work] Artist Detail, Album Dialog, Album Search, Album Missing Search

pull/6/head
Qstick 7 years ago
parent 5fec72395c
commit 0a7f18e843

@ -10,13 +10,13 @@ class AlbumStudioAlbum extends Component {
// //
// Listeners // Listeners
onSeasonMonitoredPress = () => { onAlbumMonitoredPress = () => {
const { const {
id, id,
monitored monitored
} = this.props; } = this.props;
this.props.onSeasonMonitoredPress(id, !monitored); this.props.onAlbumMonitoredPress(id, !monitored);
} }
// //
@ -43,7 +43,7 @@ class AlbumStudioAlbum extends Component {
<MonitorToggleButton <MonitorToggleButton
monitored={monitored} monitored={monitored}
isSaving={isSaving} isSaving={isSaving}
onPress={this.onSeasonMonitoredPress} onPress={this.onAlbumMonitoredPress}
/> />
<span> <span>
@ -75,7 +75,7 @@ AlbumStudioAlbum.propTypes = {
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
statistics: PropTypes.object.isRequired, statistics: PropTypes.object.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
onSeasonMonitoredPress: PropTypes.func.isRequired onAlbumMonitoredPress: PropTypes.func.isRequired
}; };
AlbumStudioAlbum.defaultProps = { AlbumStudioAlbum.defaultProps = {

@ -26,8 +26,8 @@ class AlbumStudioRow extends Component {
isSaving, isSaving,
isSelected, isSelected,
onSelectedChange, onSelectedChange,
onSeriesMonitoredPress, onArtistMonitoredPress,
onSeasonMonitoredPress onAlbumMonitoredPress
} = this.props; } = this.props;
return ( return (
@ -41,7 +41,7 @@ class AlbumStudioRow extends Component {
<TableRowCell className={styles.status}> <TableRowCell className={styles.status}>
<Icon <Icon
className={styles.statusIcon} 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'} title={status === 'ended' ? 'Ended' : 'Continuing'}
/> />
@ -58,7 +58,7 @@ class AlbumStudioRow extends Component {
<MonitorToggleButton <MonitorToggleButton
monitored={monitored} monitored={monitored}
isSaving={isSaving} isSaving={isSaving}
onPress={onSeriesMonitoredPress} onPress={onArtistMonitoredPress}
/> />
</TableRowCell> </TableRowCell>
@ -69,7 +69,7 @@ class AlbumStudioRow extends Component {
<AlbumStudioAlbum <AlbumStudioAlbum
key={season.id} key={season.id}
{...season} {...season}
onSeasonMonitoredPress={onSeasonMonitoredPress} onAlbumMonitoredPress={onAlbumMonitoredPress}
/> />
); );
}) })
@ -90,8 +90,8 @@ AlbumStudioRow.propTypes = {
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired, onSelectedChange: PropTypes.func.isRequired,
onSeriesMonitoredPress: PropTypes.func.isRequired, onArtistMonitoredPress: PropTypes.func.isRequired,
onSeasonMonitoredPress: PropTypes.func.isRequired onAlbumMonitoredPress: PropTypes.func.isRequired
}; };
AlbumStudioRow.defaultProps = { AlbumStudioRow.defaultProps = {

@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createArtistSelector from 'Store/Selectors/createArtistSelector';
import { toggleSeriesMonitored, toggleSeasonMonitored } from 'Store/Actions/artistActions'; import { toggleSeriesMonitored, toggleSeasonMonitored } from 'Store/Actions/artistActions';
import { toggleEpisodeMonitored } from 'Store/Actions/episodeActions';
import AlbumStudioRow from './AlbumStudioRow'; import AlbumStudioRow from './AlbumStudioRow';
function createMapStateToProps() { function createMapStateToProps() {
@ -25,7 +26,8 @@ function createMapStateToProps() {
const mapDispatchToProps = { const mapDispatchToProps = {
toggleSeriesMonitored, toggleSeriesMonitored,
toggleSeasonMonitored toggleSeasonMonitored,
toggleEpisodeMonitored
}; };
class AlbumStudioRowConnector extends Component { class AlbumStudioRowConnector extends Component {
@ -33,7 +35,7 @@ class AlbumStudioRowConnector extends Component {
// //
// Listeners // Listeners
onSeriesMonitoredPress = () => { onArtistMonitoredPress = () => {
const { const {
artistId, artistId,
monitored monitored
@ -45,11 +47,10 @@ class AlbumStudioRowConnector extends Component {
}); });
} }
onSeasonMonitoredPress = (seasonNumber, monitored) => { onAlbumMonitoredPress = (episodeId, monitored) => {
this.props.toggleSeasonMonitored({ this.props.toggleEpisodeMonitored({
artistId: this.props.artistId, episodeId,
seasonNumber, monitored: !monitored
monitored
}); });
} }
@ -60,8 +61,8 @@ class AlbumStudioRowConnector extends Component {
return ( return (
<AlbumStudioRow <AlbumStudioRow
{...this.props} {...this.props}
onSeriesMonitoredPress={this.onSeriesMonitoredPress} onArtistMonitoredPress={this.onArtistMonitoredPress}
onSeasonMonitoredPress={this.onSeasonMonitoredPress} onAlbumMonitoredPress={this.onAlbumMonitoredPress}
/> />
); );
} }
@ -71,7 +72,8 @@ AlbumStudioRowConnector.propTypes = {
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
toggleSeriesMonitored: PropTypes.func.isRequired, toggleSeriesMonitored: PropTypes.func.isRequired,
toggleSeasonMonitored: PropTypes.func.isRequired toggleSeasonMonitored: PropTypes.func.isRequired,
toggleEpisodeMonitored: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioRowConnector); export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioRowConnector);

@ -13,7 +13,7 @@ import AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector'
import ImportArtist from 'AddArtist/ImportArtist/ImportArtist'; import ImportArtist from 'AddArtist/ImportArtist/ImportArtist';
import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector'; import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector';
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector'; import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
import SeriesDetailsPageConnector from 'Artist/Details/SeriesDetailsPageConnector'; import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector';
import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import HistoryConnector from 'Activity/History/HistoryConnector'; import HistoryConnector from 'Activity/History/HistoryConnector';
import QueueConnector from 'Activity/Queue/QueueConnector'; import QueueConnector from 'Activity/Queue/QueueConnector';
@ -93,7 +93,7 @@ function App({ store, history }) {
<Route <Route
path="/artist/:nameSlug" path="/artist/:nameSlug"
component={SeriesDetailsPageConnector} component={ArtistDetailsPageConnector}
/> />
{/* {/*

@ -10,15 +10,6 @@
width: 42px; width: 42px;
} }
.episodeNumber {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 50px;
}
.language,
.audio,
.video,
.status { .status {
composes: cell from 'Components/Table/Cells/TableRowCell.css'; 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 createArtistSelector from 'Store/Selectors/createArtistSelector';
import createEpisodeFileSelector from 'Store/Selectors/createEpisodeFileSelector'; import createEpisodeFileSelector from 'Store/Selectors/createEpisodeFileSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector'; import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import EpisodeRow from './EpisodeRow'; import AlbumRow from './AlbumRow';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@ -17,7 +17,7 @@ function createMapStateToProps() {
const alternateTitles = sceneSeasonNumber ? _.filter(series.alternateTitles, { sceneSeasonNumber }) : []; const alternateTitles = sceneSeasonNumber ? _.filter(series.alternateTitles, { sceneSeasonNumber }) : [];
return { return {
seriesMonitored: series.monitored, artistMonitored: series.monitored,
seriesType: series.seriesType, seriesType: series.seriesType,
episodeFilePath: episodeFile ? episodeFile.path : null, episodeFilePath: episodeFile ? episodeFile.path : null,
episodeFileRelativePath: episodeFile ? episodeFile.relativePath : 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 PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import styles from './SeriesAlternateTitles.css'; import styles from './ArtistAlternateTitles.css';
function SeriesAlternateTitles({ alternateTitles }) { function ArtistAlternateTitles({ alternateTitles }) {
return ( return (
<ul> <ul>
{ {
@ -21,8 +21,8 @@ function SeriesAlternateTitles({ alternateTitles }) {
); );
} }
SeriesAlternateTitles.propTypes = { ArtistAlternateTitles.propTypes = {
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
}; };
export default SeriesAlternateTitles; export default ArtistAlternateTitles;

@ -61,11 +61,11 @@
line-height: 50px; line-height: 50px;
} }
.seriesNavigationButtons { .artistNavigationButtons {
white-space: no-wrap; white-space: no-wrap;
} }
.seriesNavigationButton { .artistNavigationButton {
composes: button from 'Components/Link/IconButton.css'; composes: button from 'Components/Link/IconButton.css';
margin-left: 5px; margin-left: 5px;
@ -90,7 +90,6 @@
.sizeOnDisk, .sizeOnDisk,
.qualityProfileName, .qualityProfileName,
.network,
.links, .links,
.tags { .tags {
margin-left: 8px; margin-left: 8px;

@ -24,11 +24,29 @@ import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfil
import ArtistPoster from 'Artist/ArtistPoster'; import ArtistPoster from 'Artist/ArtistPoster';
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector'; import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal'; import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
import SeriesAlternateTitles from './SeriesAlternateTitles'; import ArtistAlternateTitles from './ArtistAlternateTitles';
import SeriesDetailsSeasonConnector from './SeriesDetailsSeasonConnector'; import ArtistDetailsSeasonConnector from './ArtistDetailsSeasonConnector';
import SeriesTagsConnector from './SeriesTagsConnector'; import ArtistTagsConnector from './ArtistTagsConnector';
import SeriesDetailsLinks from './SeriesDetailsLinks'; import ArtistDetailsLinks from './ArtistDetailsLinks';
import styles from './SeriesDetails.css'; 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) { function getFanartUrl(images) {
const fanartImage = _.find(images, { coverType: 'fanart' }); const fanartImage = _.find(images, { coverType: 'fanart' });
@ -46,7 +64,7 @@ function getExpandedState(newState) {
}; };
} }
class SeriesDetails extends Component { class ArtistDetails extends Component {
// //
// Lifecycle // Lifecycle
@ -133,8 +151,6 @@ class SeriesDetails extends Component {
const { const {
id, id,
foreignArtistId, foreignArtistId,
tvMazeId,
imdbId,
artistName, artistName,
ratings, ratings,
sizeOnDisk, sizeOnDisk,
@ -142,7 +158,6 @@ class SeriesDetails extends Component {
qualityProfileId, qualityProfileId,
monitored, monitored,
status, status,
network,
overview, overview,
images, images,
albums, albums,
@ -154,8 +169,8 @@ class SeriesDetails extends Component {
isPopulated, isPopulated,
episodesError, episodesError,
episodeFilesError, episodeFilesError,
previousSeries, previousArtist,
nextSeries, nextArtist,
onRefreshPress, onRefreshPress,
onSearchPress onSearchPress
} = this.props; } = this.props;
@ -172,12 +187,12 @@ class SeriesDetails extends Component {
const continuing = status === 'continuing'; const continuing = status === 'continuing';
let episodeFilesCountMessage = 'No episode files'; let episodeFilesCountMessage = 'No track files';
if (trackFileCount === 1) { if (trackFileCount === 1) {
episodeFilesCountMessage = '1 episode file'; episodeFilesCountMessage = '1 track file';
} else if (trackFileCount > 1) { } else if (trackFileCount > 1) {
episodeFilesCountMessage = `${trackFileCount} episode files`; episodeFilesCountMessage = `${trackFileCount} track files`;
} }
let expandIcon = icons.EXPAND_INDETERMINATE; let expandIcon = icons.EXPAND_INDETERMINATE;
@ -217,7 +232,7 @@ class SeriesDetails extends Component {
/> />
<PageToolbarButton <PageToolbarButton
label="Manage Episodes" label="Manage Tracks"
iconName={icons.EPISODE_FILE} iconName={icons.EPISODE_FILE}
onPress={this.onManageEpisodesPress} onPress={this.onManageEpisodesPress}
/> />
@ -281,28 +296,28 @@ class SeriesDetails extends Component {
/> />
} }
title="Alternate Titles" title="Alternate Titles"
body={<SeriesAlternateTitles alternateTitles={alternateTitles} />} body={<ArtistAlternateTitles alternateTitles={alternateTitles} />}
position={tooltipPositions.BOTTOM} position={tooltipPositions.BOTTOM}
/> />
</span> </span>
} }
</div> </div>
<div className={styles.seriesNavigationButtons}> <div className={styles.artistNavigationButtons}>
<IconButton <IconButton
className={styles.seriesNavigationButton} className={styles.artistNavigationButton}
name={icons.ARROW_LEFT} name={icons.ARROW_LEFT}
size={30} size={30}
title={`Go to ${previousSeries.artistName}`} title={`Go to ${previousArtist.artistName}`}
to={`/artist/${previousSeries.nameSlug}`} to={`/artist/${previousArtist.nameSlug}`}
/> />
<IconButton <IconButton
className={styles.seriesNavigationButton} className={styles.artistNavigationButton}
name={icons.ARROW_RIGHT} name={icons.ARROW_RIGHT}
size={30} size={30}
title={`Go to ${nextSeries.artistName}`} title={`Go to ${nextArtist.artistName}`}
to={`/artist/${nextSeries.nameSlug}`} to={`/artist/${nextArtist.nameSlug}`}
/> />
</div> </div>
</div> </div>
@ -369,11 +384,11 @@ class SeriesDetails extends Component {
<Label <Label
className={styles.detailsLabel} 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} size={sizes.LARGE}
> >
<Icon <Icon
name={continuing ? icons.SERIES_CONTINUING : icons.SERIES_ENDED} name={continuing ? icons.ARTIST_CONTINUING : icons.ARTIST_ENDED}
size={17} size={17}
/> />
@ -382,24 +397,6 @@ class SeriesDetails extends Component {
</span> </span>
</Label> </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 <Tooltip
anchor={ anchor={
<Label <Label
@ -417,10 +414,8 @@ class SeriesDetails extends Component {
</Label> </Label>
} }
tooltip={ tooltip={
<SeriesDetailsLinks <ArtistDetailsLinks
foreignArtistId={foreignArtistId} foreignArtistId={foreignArtistId}
tvMazeId={tvMazeId}
imdbId={imdbId}
/> />
} }
kind={kinds.INVERSE} kind={kinds.INVERSE}
@ -445,7 +440,7 @@ class SeriesDetails extends Component {
</span> </span>
</Label> </Label>
} }
tooltip={<SeriesTagsConnector artistId={id} />} tooltip={<ArtistTagsConnector artistId={id} />}
kind={kinds.INVERSE} kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM} position={tooltipPositions.BOTTOM}
/> />
@ -477,18 +472,17 @@ class SeriesDetails extends Component {
} }
{ {
isPopulated && !!albums.length && isPopulated && !!albumTypes.length &&
<div> <div>
{ {
albums.slice(0).reverse().map((season) => { albumTypes.slice(0).map((season) => {
return ( return (
<SeriesDetailsSeasonConnector <ArtistDetailsSeasonConnector
key={season.id} key={season.name}
artistId={id} artistId={id}
albumId={season.id} label={season.label}
statistics={season.statistics}
{...season} {...season}
isExpanded={expandedState[season.id]} isExpanded={expandedState[season.name]}
onExpandPress={this.onExpandPress} onExpandPress={this.onExpandPress}
/> />
); );
@ -536,11 +530,9 @@ class SeriesDetails extends Component {
} }
} }
SeriesDetails.propTypes = { ArtistDetails.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
foreignArtistId: PropTypes.string.isRequired, foreignArtistId: PropTypes.string.isRequired,
tvMazeId: PropTypes.number,
imdbId: PropTypes.string,
artistName: PropTypes.string.isRequired, artistName: PropTypes.string.isRequired,
ratings: PropTypes.object.isRequired, ratings: PropTypes.object.isRequired,
sizeOnDisk: PropTypes.number.isRequired, sizeOnDisk: PropTypes.number.isRequired,
@ -548,7 +540,6 @@ SeriesDetails.propTypes = {
qualityProfileId: PropTypes.number.isRequired, qualityProfileId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
network: PropTypes.string,
overview: PropTypes.string.isRequired, overview: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
albums: PropTypes.arrayOf(PropTypes.object).isRequired, albums: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -560,10 +551,10 @@ SeriesDetails.propTypes = {
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
episodesError: PropTypes.object, episodesError: PropTypes.object,
episodeFilesError: PropTypes.object, episodeFilesError: PropTypes.object,
previousSeries: PropTypes.object.isRequired, previousArtist: PropTypes.object.isRequired,
nextSeries: PropTypes.object.isRequired, nextArtist: PropTypes.object.isRequired,
onRefreshPress: PropTypes.func.isRequired, onRefreshPress: PropTypes.func.isRequired,
onSearchPress: 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 { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import SeriesDetails from './SeriesDetails'; import ArtistDetails from './ArtistDetails';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@ -21,7 +21,7 @@ function createMapStateToProps() {
createAllArtistSelector(), createAllArtistSelector(),
createCommandsSelector(), createCommandsSelector(),
(nameSlug, episodes, episodeFiles, allSeries, commands) => { (nameSlug, episodes, episodeFiles, allSeries, commands) => {
const sortedArtist = _.orderBy(allSeries, 'sortTitle'); const sortedArtist = _.orderBy(allSeries, 'sortName');
const seriesIndex = _.findIndex(sortedArtist, { nameSlug }); const seriesIndex = _.findIndex(sortedArtist, { nameSlug });
const series = sortedArtist[seriesIndex]; const series = sortedArtist[seriesIndex];
@ -29,15 +29,15 @@ function createMapStateToProps() {
return {}; return {};
} }
const previousSeries = sortedArtist[seriesIndex - 1] || _.last(sortedArtist); const previousArtist = sortedArtist[seriesIndex - 1] || _.last(sortedArtist);
const nextSeries = sortedArtist[seriesIndex + 1] || _.first(sortedArtist); const nextArtist = sortedArtist[seriesIndex + 1] || _.first(sortedArtist);
const isSeriesRefreshing = !!findCommand(commands, { name: commandNames.REFRESH_ARTIST, artistId: series.id }); const isArtistRefreshing = !!findCommand(commands, { name: commandNames.REFRESH_ARTIST, artistId: series.id });
const allSeriesRefreshing = _.some(commands, (command) => command.name === commandNames.REFRESH_ARTIST && !command.body.artistId); const allArtistRefreshing = _.some(commands, (command) => command.name === commandNames.REFRESH_ARTIST && !command.body.artistId);
const isRefreshing = isSeriesRefreshing || allSeriesRefreshing; const isRefreshing = isArtistRefreshing || allArtistRefreshing;
const isSearching = !!findCommand(commands, { name: commandNames.ARTIST_SEARCH, artistId: series.id }); const isSearching = !!findCommand(commands, { name: commandNames.ARTIST_SEARCH, artistId: series.id });
const isRenamingFiles = !!findCommand(commands, { name: commandNames.RENAME_FILES, artistId: series.id }); const isRenamingFiles = !!findCommand(commands, { name: commandNames.RENAME_FILES, artistId: series.id });
const isRenamingSeriesCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST }); const isRenamingArtistCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST });
const isRenamingSeries = !!(isRenamingSeriesCommand && isRenamingSeriesCommand.body.artistId.indexOf(series.id) > -1); const isRenamingArtist = !!(isRenamingArtistCommand && isRenamingArtistCommand.body.artistId.indexOf(series.id) > -1);
const isFetching = episodes.isFetching || episodeFiles.isFetching; const isFetching = episodes.isFetching || episodeFiles.isFetching;
const isPopulated = episodes.isPopulated && episodeFiles.isPopulated; const isPopulated = episodes.isPopulated && episodeFiles.isPopulated;
@ -58,13 +58,13 @@ function createMapStateToProps() {
isRefreshing, isRefreshing,
isSearching, isSearching,
isRenamingFiles, isRenamingFiles,
isRenamingSeries, isRenamingArtist,
isFetching, isFetching,
isPopulated, isPopulated,
episodesError, episodesError,
episodeFilesError, episodeFilesError,
previousSeries, previousArtist,
nextSeries nextArtist
}; };
} }
); );
@ -80,7 +80,7 @@ const mapDispatchToProps = {
executeCommand executeCommand
}; };
class SeriesDetailsConnector extends Component { class ArtistDetailsConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -94,13 +94,13 @@ class SeriesDetailsConnector extends Component {
id, id,
isRefreshing, isRefreshing,
isRenamingFiles, isRenamingFiles,
isRenamingSeries isRenamingArtist
} = this.props; } = this.props;
if ( if (
(prevProps.isRefreshing && !isRefreshing) || (prevProps.isRefreshing && !isRefreshing) ||
(prevProps.isRenamingFiles && !isRenamingFiles) || (prevProps.isRenamingFiles && !isRenamingFiles) ||
(prevProps.isRenamingSeries && !isRenamingSeries) (prevProps.isRenamingArtist && !isRenamingArtist)
) { ) {
this._populate(); this._populate();
} }
@ -157,7 +157,7 @@ class SeriesDetailsConnector extends Component {
render() { render() {
return ( return (
<SeriesDetails <ArtistDetails
{...this.props} {...this.props}
onRefreshPress={this.onRefreshPress} onRefreshPress={this.onRefreshPress}
onSearchPress={this.onSearchPress} onSearchPress={this.onSearchPress}
@ -166,12 +166,12 @@ class SeriesDetailsConnector extends Component {
} }
} }
SeriesDetailsConnector.propTypes = { ArtistDetailsConnector.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
nameSlug: PropTypes.string.isRequired, nameSlug: PropTypes.string.isRequired,
isRefreshing: PropTypes.bool.isRequired, isRefreshing: PropTypes.bool.isRequired,
isRenamingFiles: PropTypes.bool.isRequired, isRenamingFiles: PropTypes.bool.isRequired,
isRenamingSeries: PropTypes.bool.isRequired, isRenamingArtist: PropTypes.bool.isRequired,
fetchEpisodes: PropTypes.func.isRequired, fetchEpisodes: PropTypes.func.isRequired,
clearEpisodes: PropTypes.func.isRequired, clearEpisodes: PropTypes.func.isRequired,
fetchEpisodeFiles: PropTypes.func.isRequired, fetchEpisodeFiles: PropTypes.func.isRequired,
@ -181,4 +181,4 @@ SeriesDetailsConnector.propTypes = {
executeCommand: PropTypes.func.isRequired 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 { push } from 'react-router-redux';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import NotFound from 'Components/NotFound'; import NotFound from 'Components/NotFound';
import SeriesDetailsConnector from './SeriesDetailsConnector'; import ArtistDetailsConnector from './ArtistDetailsConnector';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@ -31,7 +31,7 @@ const mapDispatchToProps = {
push push
}; };
class SeriesDetailsPageConnector extends Component { class ArtistDetailsPageConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -60,17 +60,17 @@ class SeriesDetailsPageConnector extends Component {
} }
return ( return (
<SeriesDetailsConnector <ArtistDetailsConnector
nameSlug={nameSlug} nameSlug={nameSlug}
/> />
); );
} }
} }
SeriesDetailsPageConnector.propTypes = { ArtistDetailsPageConnector.propTypes = {
nameSlug: PropTypes.string, nameSlug: PropTypes.string,
match: PropTypes.shape({ params: PropTypes.shape({ nameSlug: PropTypes.string.isRequired }).isRequired }).isRequired, match: PropTypes.shape({ params: PropTypes.shape({ nameSlug: PropTypes.string.isRequired }).isRequired }).isRequired,
push: PropTypes.func.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; margin-bottom: 20px;
border: 1px solid $borderColor; border: 1px solid $borderColor;
border-radius: 4px; border-radius: 4px;
@ -17,11 +17,17 @@
font-size: 24px; font-size: 24px;
} }
.seasonNumber { .albumTypeLabel {
margin-right: 10px; margin-right: 5px;
margin-left: 5px; margin-left: 5px;
} }
.albumCount {
font-size: 18px;
font-style: italic;
color: #8895aa;
}
.episodeCountContainer { .episodeCountContainer {
margin-left: 10px; margin-left: 10px;
vertical-align: text-bottom; vertical-align: text-bottom;
@ -38,7 +44,7 @@
.left { .left {
display: flex; display: flex;
align-items: center; align-items: center;
flex: 0 1 600px; flex: 0 1 300px;
} }
.left, .left,

@ -7,9 +7,7 @@ import getToggledRange from 'Utilities/Table/getToggledRange';
import { align, icons, kinds, sizes } from 'Helpers/Props'; import { align, icons, kinds, sizes } from 'Helpers/Props';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import Label from 'Components/Label';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import SpinnerIcon from 'Components/SpinnerIcon'; import SpinnerIcon from 'Components/SpinnerIcon';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import Menu from 'Components/Menu/Menu'; import Menu from 'Components/Menu/Menu';
@ -20,22 +18,10 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import EpisodeFileEditorModal from 'EpisodeFile/Editor/EpisodeFileEditorModal'; import EpisodeFileEditorModal from 'EpisodeFile/Editor/EpisodeFileEditorModal';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector'; import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
import EpisodeRowConnector from './EpisodeRowConnector'; import AlbumRowConnector from './AlbumRowConnector';
import styles from './SeriesDetailsSeason.css'; import styles from './ArtistDetailsSeason.css';
function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) { class ArtistDetailsSeason extends Component {
if (episodeFileCount === episodeCount && episodeCount > 0) {
return kinds.SUCCESS;
}
if (!monitored) {
return kinds.WARNING;
}
return kinds.DANGER;
}
class SeriesDetailsSeason extends Component {
// //
// Lifecycle // Lifecycle
@ -65,7 +51,7 @@ class SeriesDetailsSeason extends Component {
_expandByDefault() { _expandByDefault() {
const { const {
albumId, name,
onExpandPress, onExpandPress,
items items
} = this.props; } = this.props;
@ -75,7 +61,7 @@ class SeriesDetailsSeason extends Component {
isAfter(item.airDateUtc, { days: -30 }); isAfter(item.airDateUtc, { days: -30 });
}); });
onExpandPress(albumId, expand && albumId > 0); onExpandPress(name, expand && name > 0);
} }
// //
@ -99,29 +85,29 @@ class SeriesDetailsSeason extends Component {
onExpandPress = () => { onExpandPress = () => {
const { const {
albumId, name,
isExpanded isExpanded
} = this.props; } = 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 lastToggled = this.state.lastToggledEpisode;
const episodeIds = [episodeId]; const albumIds = [albumId];
if (shiftKey && lastToggled) { 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; const items = this.props.items;
for (let i = lower; i < upper; i++) { 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() { render() {
const { const {
artistId, artistId,
monitored, label,
title,
releaseDate,
albumId,
statistics,
items, items,
columns, columns,
isSaving, isSaving,
isExpanded, isExpanded,
isSearching, isSearching,
seriesMonitored, artistMonitored,
isSmallScreen, isSmallScreen,
onTableOptionChange, onTableOptionChange,
onMonitorSeasonPress, onMonitorSeasonPress,
onSearchPress onSearchPress
} = this.props; } = this.props;
const {
trackCount,
trackFileCount,
totalTrackCount
} = statistics;
const { const {
isOrganizeModalOpen, isOrganizeModalOpen,
isManageEpisodesOpen isManageEpisodesOpen
@ -160,37 +136,22 @@ class SeriesDetailsSeason extends Component {
return ( return (
<div <div
className={styles.season} className={styles.albumType}
> >
<div className={styles.header}> <div className={styles.header}>
<div className={styles.left}> <div className={styles.left}>
<MonitorToggleButton
monitored={monitored}
isDisabled={!seriesMonitored}
isSaving={isSaving}
size={24}
onPress={onMonitorSeasonPress}
/>
{ {
albumId === 0 ? <div>
<span className={styles.seasonNumber}> <span className={styles.albumTypeLabel}>
Specials {label}
</span> :
<span className={styles.seasonNumber}>
{title}
</span> </span>
}
<Label <span className={styles.albumCount}>
title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`} ({items.length} Releases)
kind={getEpisodeCountKind(monitored, trackFileCount, trackCount)} </span>
size={sizes.LARGE} </div>
>
{
<span>{trackFileCount} / {trackCount}</span>
} }
</Label>
</div> </div>
<Link <Link
@ -198,6 +159,13 @@ class SeriesDetailsSeason extends Component {
onPress={this.onExpandPress} onPress={this.onExpandPress}
> >
<Icon
className={styles.expandButtonIcon}
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
title={isExpanded ? 'Hide albums' : 'Show albums'}
size={24}
/>
{ {
!isSmallScreen && !isSmallScreen &&
<span>&nbsp;</span> <span>&nbsp;</span>
@ -266,37 +234,62 @@ class SeriesDetailsSeason extends Component {
onPress={onSearchPress} onPress={onSearchPress}
/> />
<IconButton </div>
className={styles.actionButton} }
name={icons.ORGANIZE}
title="Preview rename for this album" </div>
size={24}
onPress={this.onOrganizePress} <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 <IconButton
className={styles.actionButton} name={icons.COLLAPSE}
name={icons.EPISODE_FILE} size={20}
title="Manage track files in this artist" title="Hide episodes"
size={24} onPress={this.onExpandPress}
onPress={this.onManageEpisodesPress}
/> />
</div> </div>
</div>
} }
</div> </div>
<OrganizePreviewModalConnector <OrganizePreviewModalConnector
isOpen={isOrganizeModalOpen} isOpen={isOrganizeModalOpen}
artistId={artistId} artistId={artistId}
albumId={albumId}
onModalClose={this.onOrganizeModalClose} onModalClose={this.onOrganizeModalClose}
/> />
<EpisodeFileEditorModal <EpisodeFileEditorModal
isOpen={isManageEpisodesOpen} isOpen={isManageEpisodesOpen}
artistId={artistId} artistId={artistId}
albumId={albumId}
onModalClose={this.onManageEpisodesModalClose} onModalClose={this.onManageEpisodesModalClose}
/> />
</div> </div>
@ -304,33 +297,22 @@ class SeriesDetailsSeason extends Component {
} }
} }
SeriesDetailsSeason.propTypes = { ArtistDetailsSeason.propTypes = {
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired, name: PropTypes.string.isRequired,
title: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
releaseDate: PropTypes.string.isRequired,
albumId: PropTypes.number.isRequired,
statistics: PropTypes.object.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool, isSaving: PropTypes.bool,
isExpanded: PropTypes.bool, isExpanded: PropTypes.bool,
isSearching: PropTypes.bool.isRequired, isSearching: PropTypes.bool.isRequired,
seriesMonitored: PropTypes.bool.isRequired, artistMonitored: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
onTableOptionChange: PropTypes.func.isRequired, onTableOptionChange: PropTypes.func.isRequired,
onMonitorSeasonPress: PropTypes.func.isRequired, onMonitorSeasonPress: PropTypes.func.isRequired,
onExpandPress: PropTypes.func.isRequired, onExpandPress: PropTypes.func.isRequired,
onMonitorEpisodePress: PropTypes.func.isRequired, onMonitorAlbumPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired onSearchPress: PropTypes.func.isRequired
}; };
SeriesDetailsSeason.defaultProps = { export default ArtistDetailsSeason;
statistics: {
trackFileCount: 0,
totalTrackCount: 0,
percentOfTracks: 0
}
};
export default SeriesDetailsSeason;

@ -11,30 +11,30 @@ import { toggleSeasonMonitored } from 'Store/Actions/artistActions';
import { toggleEpisodesMonitored, setEpisodesTableOption } from 'Store/Actions/episodeActions'; import { toggleEpisodesMonitored, setEpisodesTableOption } from 'Store/Actions/episodeActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import SeriesDetailsSeason from './SeriesDetailsSeason'; import ArtistDetailsSeason from './ArtistDetailsSeason';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { seasonNumber }) => seasonNumber, (state, { label }) => label,
(state) => state.episodes, (state) => state.episodes,
createArtistSelector(), createArtistSelector(),
createCommandsSelector(), createCommandsSelector(),
createDimensionsSelector(), createDimensionsSelector(),
(seasonNumber, episodes, series, commands, dimensions) => { (label, episodes, series, commands, dimensions) => {
const isSearching = !!findCommand(commands, { const isSearching = !!findCommand(commands, {
name: commandNames.SEASON_SEARCH, name: commandNames.SEASON_SEARCH,
artistId: series.id, artistId: series.id,
seasonNumber label
}); });
const episodesInSeason = _.filter(episodes.items, { seasonNumber }); const episodesInSeason = _.filter(episodes.items, { albumType: label });
const sortedEpisodes = _.orderBy(episodesInSeason, 'episodeNumber', 'desc'); const sortedEpisodes = _.orderBy(episodesInSeason, 'releaseDate', 'desc');
return { return {
items: sortedEpisodes, items: sortedEpisodes,
columns: episodes.columns, columns: episodes.columns,
isSearching, isSearching,
seriesMonitored: series.monitored, artistMonitored: series.monitored,
isSmallScreen: dimensions.isSmallScreen isSmallScreen: dimensions.isSmallScreen
}; };
} }
@ -48,7 +48,7 @@ const mapDispatchToProps = {
executeCommand executeCommand
}; };
class SeriesDetailsSeasonConnector extends Component { class ArtistDetailsSeasonConnector extends Component {
// //
// Listeners // Listeners
@ -59,33 +59,29 @@ class SeriesDetailsSeasonConnector extends Component {
onMonitorSeasonPress = (monitored) => { onMonitorSeasonPress = (monitored) => {
const { const {
artistId, artistId
albumId
} = this.props; } = this.props;
this.props.toggleSeasonMonitored({ this.props.toggleSeasonMonitored({
artistId, artistId,
albumId,
monitored monitored
}); });
} }
onSearchPress = () => { onSearchPress = () => {
const { const {
artistId, artistId
albumId
} = this.props; } = this.props;
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.SEASON_SEARCH, name: commandNames.SEASON_SEARCH,
artistId, artistId
albumIds: [albumId]
}); });
} }
onMonitorEpisodePress = (episodeIds, monitored) => { onMonitorAlbumPress = (albumIds, monitored) => {
this.props.toggleEpisodesMonitored({ this.props.toggleEpisodesMonitored({
episodeIds, albumIds,
monitored monitored
}); });
} }
@ -95,24 +91,23 @@ class SeriesDetailsSeasonConnector extends Component {
render() { render() {
return ( return (
<SeriesDetailsSeason <ArtistDetailsSeason
{...this.props} {...this.props}
onTableOptionChange={this.onTableOptionChange} onTableOptionChange={this.onTableOptionChange}
onMonitorSeasonPress={this.onMonitorSeasonPress} onMonitorSeasonPress={this.onMonitorSeasonPress}
onSearchPress={this.onSearchPress} onSearchPress={this.onSearchPress}
onMonitorEpisodePress={this.onMonitorEpisodePress} onMonitorAlbumPress={this.onMonitorAlbumPress}
/> />
); );
} }
} }
SeriesDetailsSeasonConnector.propTypes = { ArtistDetailsSeasonConnector.propTypes = {
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
albumId: PropTypes.number.isRequired,
toggleSeasonMonitored: PropTypes.func.isRequired, toggleSeasonMonitored: PropTypes.func.isRequired,
toggleEpisodesMonitored: PropTypes.func.isRequired, toggleEpisodesMonitored: PropTypes.func.isRequired,
setEpisodesTableOption: PropTypes.func.isRequired, setEpisodesTableOption: PropTypes.func.isRequired,
executeCommand: 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 React from 'react';
import { kinds, sizes } from 'Helpers/Props'; import { kinds, sizes } from 'Helpers/Props';
import Label from 'Components/Label'; import Label from 'Components/Label';
import styles from './SeriesTags.css'; import styles from './ArtistTags.css';
function SeriesTags({ tags }) { function ArtistTags({ tags }) {
return ( return (
<div> <div>
{ {
@ -24,8 +24,8 @@ function SeriesTags({ tags }) {
); );
} }
SeriesTags.propTypes = { ArtistTags.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string).isRequired 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 { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector'; import createTagsSelector from 'Store/Selectors/createTagsSelector';
import SeriesTags from './SeriesTags'; import ArtistTags from './ArtistTags';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( 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 <Icon
className={styles.statusIcon} 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'} 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_LOG_FILES = 'DeleteLogFiles';
export const DELETE_UPDATE_LOG_FILES = 'DeleteUpdateLogFiles'; export const DELETE_UPDATE_LOG_FILES = 'DeleteUpdateLogFiles';
export const DOWNLOADED_EPSIODES_SCAN = 'DownloadedEpisodesScan'; export const DOWNLOADED_EPSIODES_SCAN = 'DownloadedEpisodesScan';
export const EPISODE_SEARCH = 'EpisodeSearch'; export const EPISODE_SEARCH = 'AlbumSearch';
export const INTERACTIVE_IMPORT = 'ManualImport'; export const INTERACTIVE_IMPORT = 'ManualImport';
export const MISSING_EPISODE_SEARCH = 'MissingEpisodeSearch'; export const MISSING_ALBUM_SEARCH = 'MissingAlbumSearch';
export const REFRESH_ARTIST = 'RefreshArtist'; export const REFRESH_ARTIST = 'RefreshArtist';
export const RENAME_FILES = 'RenameFiles'; export const RENAME_FILES = 'RenameFiles';
export const RENAME_ARTIST = 'RenameArtist'; export const RENAME_ARTIST = 'RenameArtist';

@ -19,7 +19,7 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
const links = [ const links = [
{ {
iconName: icons.SERIES_CONTINUING, iconName: icons.ARTIST_CONTINUING,
title: 'Artist', title: 'Artist',
to: '/', to: '/',
alias: '/series', alias: '/series',

@ -11,7 +11,6 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
import EpisodeSummaryConnector from './Summary/EpisodeSummaryConnector'; import EpisodeSummaryConnector from './Summary/EpisodeSummaryConnector';
import EpisodeHistoryConnector from './History/EpisodeHistoryConnector'; import EpisodeHistoryConnector from './History/EpisodeHistoryConnector';
import EpisodeSearchConnector from './Search/EpisodeSearchConnector'; import EpisodeSearchConnector from './Search/EpisodeSearchConnector';
import SeasonEpisodeNumber from './SeasonEpisodeNumber';
import styles from './EpisodeDetailsModalContent.css'; import styles from './EpisodeDetailsModalContent.css';
const tabs = [ const tabs = [
@ -47,26 +46,22 @@ class EpisodeDetailsModalContent extends Component {
const { const {
episodeId, episodeId,
episodeEntity, episodeEntity,
episodeFileId,
artistId, artistId,
seriesTitle, seriesTitle,
titleSlug, nameSlug,
seriesMonitored, albumLabel,
seriesType, artistMonitored,
seasonNumber,
episodeNumber,
absoluteEpisodeNumber,
episodeTitle, episodeTitle,
airDate, releaseDate,
monitored, monitored,
isSaving, isSaving,
showOpenSeriesButton, showOpenSeriesButton,
startInteractiveSearch, startInteractiveSearch,
onMonitorEpisodePress, onMonitorAlbumPress,
onModalClose onModalClose
} = this.props; } = this.props;
const seriesLink = `/artist/${titleSlug}`; const seriesLink = `/artist/${nameSlug}`;
return ( return (
<ModalContent <ModalContent
@ -78,9 +73,9 @@ class EpisodeDetailsModalContent extends Component {
id={episodeId} id={episodeId}
monitored={monitored} monitored={monitored}
size={18} size={18}
isDisabled={!seriesMonitored} isDisabled={!artistMonitored}
isSaving={isSaving} isSaving={isSaving}
onPress={onMonitorEpisodePress} onPress={onMonitorAlbumPress}
/> />
<span className={styles.seriesTitle}> <span className={styles.seriesTitle}>
@ -89,16 +84,6 @@ class EpisodeDetailsModalContent extends Component {
<span className={styles.separator}>-</span> <span className={styles.separator}>-</span>
<SeasonEpisodeNumber
seasonNumber={seasonNumber}
episodeNumber={episodeNumber}
absoluteEpisodeNumber={absoluteEpisodeNumber}
airDate={airDate}
seriesType={seriesType}
/>
<span className={styles.separator}>-</span>
{episodeTitle} {episodeTitle}
</ModalHeader> </ModalHeader>
@ -137,7 +122,8 @@ class EpisodeDetailsModalContent extends Component {
<EpisodeSummaryConnector <EpisodeSummaryConnector
episodeId={episodeId} episodeId={episodeId}
episodeEntity={episodeEntity} episodeEntity={episodeEntity}
episodeFileId={episodeFileId} releaseDate={releaseDate}
albumLabel={albumLabel}
artistId={artistId} artistId={artistId}
/> />
</TabPanel> </TabPanel>
@ -150,7 +136,7 @@ class EpisodeDetailsModalContent extends Component {
<TabPanel className={styles.tabPanel}> <TabPanel className={styles.tabPanel}>
<EpisodeSearchConnector <EpisodeSearchConnector
episodeId={episodeId} albumId={episodeId}
startInteractiveSearch={startInteractiveSearch} startInteractiveSearch={startInteractiveSearch}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -166,7 +152,7 @@ class EpisodeDetailsModalContent extends Component {
to={seriesLink} to={seriesLink}
onPress={onModalClose} onPress={onModalClose}
> >
Open Series Open Artist
</Button> </Button>
} }
@ -184,28 +170,25 @@ class EpisodeDetailsModalContent extends Component {
EpisodeDetailsModalContent.propTypes = { EpisodeDetailsModalContent.propTypes = {
episodeId: PropTypes.number.isRequired, episodeId: PropTypes.number.isRequired,
episodeEntity: PropTypes.string.isRequired, episodeEntity: PropTypes.string.isRequired,
episodeFileId: PropTypes.number,
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
seriesTitle: PropTypes.string.isRequired, seriesTitle: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired, nameSlug: PropTypes.string.isRequired,
seriesMonitored: PropTypes.bool.isRequired, artistMonitored: PropTypes.bool.isRequired,
seriesType: PropTypes.string.isRequired, releaseDate: PropTypes.string.isRequired,
seasonNumber: PropTypes.number.isRequired, albumLabel: PropTypes.string.isRequired,
episodeNumber: PropTypes.number.isRequired,
absoluteEpisodeNumber: PropTypes.number,
airDate: PropTypes.string.isRequired,
episodeTitle: PropTypes.string.isRequired, episodeTitle: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
isSaving: PropTypes.bool, isSaving: PropTypes.bool,
showOpenSeriesButton: PropTypes.bool, showOpenSeriesButton: PropTypes.bool,
selectedTab: PropTypes.string.isRequired, selectedTab: PropTypes.string.isRequired,
startInteractiveSearch: PropTypes.bool.isRequired, startInteractiveSearch: PropTypes.bool.isRequired,
onMonitorEpisodePress: PropTypes.func.isRequired, onMonitorAlbumPress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
EpisodeDetailsModalContent.defaultProps = { EpisodeDetailsModalContent.defaultProps = {
selectedTab: 'details', selectedTab: 'details',
albumLabel: 'Unknown',
episodeEntity: episodeEntities.EPISODES, episodeEntity: episodeEntities.EPISODES,
startInteractiveSearch: false startInteractiveSearch: false
}; };

@ -15,16 +15,16 @@ function createMapStateToProps() {
createArtistSelector(), createArtistSelector(),
(episode, series) => { (episode, series) => {
const { const {
title: seriesTitle, artistName: seriesTitle,
titleSlug, nameSlug,
monitored: seriesMonitored, monitored: artistMonitored,
seriesType seriesType
} = series; } = series;
return { return {
seriesTitle, seriesTitle,
titleSlug, nameSlug,
seriesMonitored, artistMonitored,
seriesType, seriesType,
...episode ...episode
}; };
@ -52,7 +52,7 @@ class EpisodeDetailsModalContentConnector extends Component {
// //
// Listeners // Listeners
onMonitorEpisodePress = (monitored) => { onMonitorAlbumPress = (monitored) => {
const { const {
episodeId, episodeId,
episodeEntity episodeEntity
@ -72,7 +72,7 @@ class EpisodeDetailsModalContentConnector extends Component {
return ( return (
<EpisodeDetailsModalContent <EpisodeDetailsModalContent
{...this.props} {...this.props}
onMonitorEpisodePress={this.onMonitorEpisodePress} onMonitorAlbumPress={this.onMonitorAlbumPress}
/> />
); );
} }

@ -21,11 +21,11 @@ function createMapStateToProps() {
return false; return false;
} }
return command.body.episodeIds.indexOf(episodeId) > -1; return command.body.albumIds.indexOf(episodeId) > -1;
}); });
return { return {
seriesMonitored: series.monitored, artistMonitored: series.monitored,
seriesType: series.seriesType, seriesType: series.seriesType,
isSearching isSearching
}; };
@ -38,7 +38,7 @@ function createMapDispatchToProps(dispatch, props) {
onSearchPress(name, path) { onSearchPress(name, path) {
dispatch(executeCommand({ dispatch(executeCommand({
name: commandNames.EPISODE_SEARCH, name: commandNames.EPISODE_SEARCH,
episodeIds: [props.episodeId] albumIds: [props.episodeId]
})); }));
} }
}; };

@ -61,13 +61,13 @@ class EpisodeHistory extends Component {
if (!isFetching && !!error) { if (!isFetching && !!error) {
return ( return (
<div>Unable to load episode history.</div> <div>Unable to load album history.</div>
); );
} }
if (isPopulated && !hasItems && !error) { if (isPopulated && !hasItems && !error) {
return ( return (
<div>No episode history.</div> <div>No album history.</div>
); );
} }

@ -47,7 +47,7 @@ class EpisodeSearchConnector extends Component {
onQuickSearchPress = () => { onQuickSearchPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.EPISODE_SEARCH, name: commandNames.EPISODE_SEARCH,
episodeIds: [this.props.episodeId] albumIds: [this.props.albumId]
}); });
this.props.onModalClose(); this.props.onModalClose();
@ -80,7 +80,7 @@ class EpisodeSearchConnector extends Component {
} }
EpisodeSearchConnector.propTypes = { EpisodeSearchConnector.propTypes = {
episodeId: PropTypes.number.isRequired, albumId: PropTypes.number.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
startInteractiveSearch: PropTypes.bool.isRequired, startInteractiveSearch: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,

@ -34,7 +34,7 @@ class InteractiveEpisodeSearchConnector extends Component {
componentDidMount() { componentDidMount() {
const { const {
episodeId, albumId,
isPopulated isPopulated
} = this.props; } = this.props;
@ -43,7 +43,7 @@ class InteractiveEpisodeSearchConnector extends Component {
if (!isPopulated) { if (!isPopulated) {
this.props.fetchReleases({ this.props.fetchReleases({
episodeId albumId
}); });
} }
} }
@ -74,7 +74,7 @@ class InteractiveEpisodeSearchConnector extends Component {
} }
InteractiveEpisodeSearchConnector.propTypes = { InteractiveEpisodeSearchConnector.propTypes = {
episodeId: PropTypes.number.isRequired, albumId: PropTypes.number.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
fetchReleases: PropTypes.func.isRequired, fetchReleases: PropTypes.func.isRequired,
setReleasesSort: PropTypes.func.isRequired, setReleasesSort: PropTypes.func.isRequired,

@ -10,8 +10,8 @@ import Label from 'Components/Label';
function EpisodeAiring(props) { function EpisodeAiring(props) {
const { const {
airDateUtc, releaseDate,
network, albumLabel,
shortDateFormat, shortDateFormat,
showRelativeDates, showRelativeDates,
timeFormat timeFormat
@ -22,11 +22,11 @@ function EpisodeAiring(props) {
kind={kinds.INFO} kind={kinds.INFO}
size={sizes.MEDIUM} size={sizes.MEDIUM}
> >
{network} {albumLabel}
</Label> </Label>
); );
if (!airDateUtc) { if (!releaseDate) {
return ( return (
<span> <span>
TBA on {networkLabel} TBA on {networkLabel}
@ -34,50 +34,48 @@ function EpisodeAiring(props) {
); );
} }
const time = formatTime(airDateUtc, timeFormat);
if (!showRelativeDates) { if (!showRelativeDates) {
return ( return (
<span> <span>
{moment(airDateUtc).format(shortDateFormat)} at {time} on {networkLabel} {moment(releaseDate).format(shortDateFormat)} on {networkLabel}
</span> </span>
); );
} }
if (isToday(airDateUtc)) { if (isToday(releaseDate)) {
return ( return (
<span> <span>
{time} on {networkLabel} Today on {networkLabel}
</span> </span>
); );
} }
if (isTomorrow(airDateUtc)) { if (isTomorrow(releaseDate)) {
return ( return (
<span> <span>
Tomorrow at {time} on {networkLabel} Tomorrow on {networkLabel}
</span> </span>
); );
} }
if (isInNextWeek(airDateUtc)) { if (isInNextWeek(releaseDate)) {
return ( return (
<span> <span>
{moment(airDateUtc).format('dddd')} at {time} on {networkLabel} {moment(releaseDate).format('dddd')} on {networkLabel}
</span> </span>
); );
} }
return ( return (
<span> <span>
{moment(airDateUtc).format(shortDateFormat)} at {time} on {networkLabel} {moment(releaseDate).format(shortDateFormat)} on {networkLabel}
</span> </span>
); );
} }
EpisodeAiring.propTypes = { EpisodeAiring.propTypes = {
airDateUtc: PropTypes.string.isRequired, releaseDate: PropTypes.string.isRequired,
network: PropTypes.string.isRequired, albumLabel: PropTypes.string.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
showRelativeDates: PropTypes.bool.isRequired, showRelativeDates: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired timeFormat: PropTypes.string.isRequired

@ -45,9 +45,9 @@ class EpisodeSummary extends Component {
render() { render() {
const { const {
qualityProfileId, qualityProfileId,
network,
overview, overview,
airDateUtc, releaseDate,
albumLabel,
path, path,
size, size,
quality, quality,
@ -59,11 +59,11 @@ class EpisodeSummary extends Component {
return ( return (
<div> <div>
<div> <div>
<span className={styles.infoTitle}>Airs</span> <span className={styles.infoTitle}>Releases</span>
<EpisodeAiringConnector <EpisodeAiringConnector
airDateUtc={airDateUtc} releaseDate={releaseDate}
network={network} albumLabel={albumLabel}
/> />
</div> </div>
@ -84,7 +84,7 @@ class EpisodeSummary extends Component {
{ {
hasOverview ? hasOverview ?
overview : overview :
'No episode overview.' 'No album overview.'
} }
</div> </div>
@ -151,11 +151,10 @@ class EpisodeSummary extends Component {
} }
EpisodeSummary.propTypes = { EpisodeSummary.propTypes = {
episodeFileId: PropTypes.number.isRequired,
qualityProfileId: PropTypes.number.isRequired, qualityProfileId: PropTypes.number.isRequired,
network: PropTypes.string.isRequired,
overview: PropTypes.string, overview: PropTypes.string,
airDateUtc: PropTypes.string.isRequired, albumLabel: PropTypes.string,
releaseDate: PropTypes.string.isRequired,
path: PropTypes.string, path: PropTypes.string,
size: PropTypes.number, size: PropTypes.number,
quality: PropTypes.object, quality: PropTypes.object,

@ -67,8 +67,8 @@ export const RSS = 'fa fa-rss';
export const SAVE = 'fa fa-floppy-o'; export const SAVE = 'fa fa-floppy-o';
export const SCHEDULED = 'fa fa-clock-o'; export const SCHEDULED = 'fa fa-clock-o';
export const SEARCH = 'fa fa-search'; export const SEARCH = 'fa fa-search';
export const SERIES_CONTINUING = 'fa fa-play'; export const ARTIST_CONTINUING = 'fa fa-play';
export const SERIES_ENDED = 'fa fa-stop'; export const ARTIST_ENDED = 'fa fa-stop';
export const SETTINGS = 'fa fa-cogs'; export const SETTINGS = 'fa fa-cogs';
export const SHUTDOWN = 'fa fa-power-off'; export const SHUTDOWN = 'fa fa-power-off';
export const SORT = 'fa fa-sort'; export const SORT = 'fa fa-sort';

@ -9,7 +9,7 @@ import { updateItem } from './baseActions';
const section = 'episodes'; const section = 'episodes';
const episodeActionHandlers = { const episodeActionHandlers = {
[types.FETCH_EPISODES]: createFetchHandler(section, '/track'), [types.FETCH_EPISODES]: createFetchHandler(section, '/album'),
[types.TOGGLE_EPISODE_MONITORED]: function(payload) { [types.TOGGLE_EPISODE_MONITORED]: function(payload) {
return function(dispatch, getState) { return function(dispatch, getState) {
@ -28,7 +28,7 @@ const episodeActionHandlers = {
})); }));
const promise = $.ajax({ const promise = $.ajax({
url: `/episode/${id}`, url: `/album/${id}`,
method: 'PUT', method: 'PUT',
data: JSON.stringify({ monitored }), data: JSON.stringify({ monitored }),
dataType: 'json' dataType: 'json'
@ -56,7 +56,7 @@ const episodeActionHandlers = {
[types.TOGGLE_EPISODES_MONITORED]: function(payload) { [types.TOGGLE_EPISODES_MONITORED]: function(payload) {
return function(dispatch, getState) { return function(dispatch, getState) {
const { const {
episodeIds, albumIds,
episodeEntity = episodeEntities.EPISODES, episodeEntity = episodeEntities.EPISODES,
monitored monitored
} = payload; } = payload;
@ -64,7 +64,7 @@ const episodeActionHandlers = {
const episodeSection = _.last(episodeEntity.split('.')); const episodeSection = _.last(episodeEntity.split('.'));
dispatch(batchActions( dispatch(batchActions(
episodeIds.map((episodeId) => { albumIds.map((episodeId) => {
return updateItem({ return updateItem({
id: episodeId, id: episodeId,
section: episodeSection, section: episodeSection,
@ -74,15 +74,15 @@ const episodeActionHandlers = {
)); ));
const promise = $.ajax({ const promise = $.ajax({
url: '/episode/monitor', url: '/album/monitor',
method: 'PUT', method: 'PUT',
data: JSON.stringify({ episodeIds, monitored }), data: JSON.stringify({ albumIds, monitored }),
dataType: 'json' dataType: 'json'
}); });
promise.done((data) => { promise.done((data) => {
dispatch(batchActions( dispatch(batchActions(
episodeIds.map((episodeId) => { albumIds.map((episodeId) => {
return updateItem({ return updateItem({
id: episodeId, id: episodeId,
section: episodeSection, section: episodeSection,
@ -95,7 +95,7 @@ const episodeActionHandlers = {
promise.fail((xhr) => { promise.fail((xhr) => {
dispatch(batchActions( dispatch(batchActions(
episodeIds.map((episodeId) => { albumIds.map((episodeId) => {
return updateItem({ return updateItem({
id: episodeId, id: episodeId,
section: episodeSection, section: episodeSection,

@ -11,7 +11,7 @@ export const defaultState = {
isFetching: false, isFetching: false,
isPopulated: false, isPopulated: false,
error: null, error: null,
sortKey: 'episodeNumber', sortKey: 'releaseDate',
sortDirection: sortDirections.DESCENDING, sortDirection: sortDirections.DESCENDING,
items: [], items: [],
@ -22,11 +22,6 @@ export const defaultState = {
isVisible: true, isVisible: true,
isModifiable: false isModifiable: false
}, },
{
name: 'episodeNumber',
label: '#',
isVisible: true
},
{ {
name: 'title', name: 'title',
label: 'Title', label: 'Title',
@ -38,28 +33,18 @@ export const defaultState = {
isVisible: false isVisible: false
}, },
{ {
name: 'relativePath', name: 'releaseDate',
label: 'Relative Path', label: 'Release Date',
isVisible: false
},
{
name: 'airDateUtc',
label: 'Air Date',
isVisible: true isVisible: true
}, },
{ {
name: 'language', name: 'trackCount',
label: 'Language', label: 'Track Count',
isVisible: false
},
{
name: 'audioInfo',
label: 'Audio Info',
isVisible: false isVisible: false
}, },
{ {
name: 'videoCodec', name: 'duration',
label: 'Video Codec', label: 'Duration',
isVisible: false isVisible: false
}, },
{ {

@ -110,7 +110,7 @@ class CutoffUnmetConnector extends Component {
onSearchSelectedPress = (selected) => { onSearchSelectedPress = (selected) => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.EPISODE_SEARCH, name: commandNames.EPISODE_SEARCH,
episodeIds: selected albumIds: selected
}); });
} }

@ -18,7 +18,7 @@ function createMapStateToProps() {
createCommandsSelector(), createCommandsSelector(),
(missing, commands) => { (missing, commands) => {
const isSearchingForAlbums = _.some(commands, { name: commandNames.EPISODE_SEARCH }); 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 { return {
isSearchingForAlbums, isSearchingForAlbums,
@ -100,7 +100,7 @@ class MissingConnector extends Component {
onSearchSelectedPress = (selected) => { onSearchSelectedPress = (selected) => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.EPISODE_SEARCH, name: commandNames.EPISODE_SEARCH,
episodeIds: selected albumIds: selected
}); });
} }
@ -118,7 +118,7 @@ class MissingConnector extends Component {
onSearchAllMissingPress = () => { onSearchAllMissingPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.MISSING_EPISODE_SEARCH name: commandNames.MISSING_ALBUM_SEARCH
}); });
} }

Loading…
Cancel
Save