Add Tracklist to Album Modal

This commit is contained in:
Qstick 2017-09-24 15:44:25 -04:00
parent 90d9741056
commit d243a8c8c4
16 changed files with 283 additions and 104 deletions

View file

@ -188,7 +188,7 @@ EpisodeDetailsModalContent.propTypes = {
EpisodeDetailsModalContent.defaultProps = {
selectedTab: 'details',
albumLabel: 'Unknown',
albumLabel: ['Unknown'],
episodeEntity: episodeEntities.EPISODES,
startInteractiveSearch: false
};

View file

@ -7,6 +7,8 @@ import { toggleEpisodeMonitored } from 'Store/Actions/episodeActions';
import createEpisodeSelector from 'Store/Selectors/createEpisodeSelector';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import episodeEntities from 'Episode/episodeEntities';
import { fetchTracks, clearTracks } from 'Store/Actions/trackActions';
import { fetchEpisodeFiles, clearEpisodeFiles } from 'Store/Actions/episodeFileActions';
import EpisodeDetailsModalContent from './EpisodeDetailsModalContent';
function createMapStateToProps() {
@ -34,6 +36,10 @@ function createMapStateToProps() {
const mapDispatchToProps = {
clearReleases,
fetchTracks,
clearTracks,
fetchEpisodeFiles,
clearEpisodeFiles,
toggleEpisodeMonitored
};
@ -41,14 +47,33 @@ class EpisodeDetailsModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
this._populate();
}
componentWillUnmount() {
// Clear pending releases here so we can reshow the search
// results even after switching tabs.
this._unpopulate();
this.props.clearReleases();
}
//
// Control
_populate() {
const artistId = this.props.artistId;
const albumId = this.props.episodeId;
this.props.fetchTracks({ artistId, albumId });
// this.props.fetchEpisodeFiles({ artistId, albumId });
}
_unpopulate() {
this.props.clearTracks();
// this.props.clearEpisodeFiles();
}
//
// Listeners
@ -82,6 +107,10 @@ EpisodeDetailsModalContentConnector.propTypes = {
episodeId: PropTypes.number.isRequired,
episodeEntity: PropTypes.string.isRequired,
artistId: PropTypes.number.isRequired,
fetchTracks: PropTypes.func.isRequired,
clearTracks: PropTypes.func.isRequired,
fetchEpisodeFiles: PropTypes.func.isRequired,
clearEpisodeFiles: PropTypes.func.isRequired,
clearReleases: PropTypes.func.isRequired,
toggleEpisodeMonitored: PropTypes.func.isRequired
};

View file

@ -118,7 +118,7 @@ function EpisodeStatus(props) {
EpisodeStatus.propTypes = {
airDateUtc: PropTypes.string,
monitored: PropTypes.bool.isRequired,
monitored: PropTypes.bool,
grabbed: PropTypes.bool,
queueItem: PropTypes.object,
episodeFile: PropTypes.object

View file

@ -7,7 +7,10 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import Label from 'Components/Label';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import EpisodeQuality from 'Episode/EpisodeQuality';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import EpisodeAiringConnector from './EpisodeAiringConnector';
import TrackDetailRow from './TrackDetailRow';
import styles from './EpisodeSummary.css';
class EpisodeSummary extends Component {
@ -49,9 +52,11 @@ class EpisodeSummary extends Component {
releaseDate,
albumLabel,
path,
items,
size,
quality,
qualityCutoffNotMet
qualityCutoffNotMet,
columns
} = this.props;
const hasOverview = !!overview;
@ -88,53 +93,36 @@ class EpisodeSummary extends Component {
}
</div>
{
path &&
<div className={styles.files}>
<div className={styles.filesHeader}>
<div className={styles.path}>
Path
</div>
<div>
{
<div className={styles.episodes}>
{
items.length ?
<Table
columns={columns}
>
<TableBody>
{
items.map((item) => {
return (
<TrackDetailRow
key={item.id}
columns={columns}
{...item}
/>
);
})
}
</TableBody>
</Table> :
<div className={styles.size}>
Size
</div>
<div className={styles.quality}>
Quality
</div>
<div className={styles.actions}></div>
</div>
<div className={styles.fileRow}>
<div
className={styles.path}
title={path}
>
{path}
</div>
<div className={styles.size}>
{formatBytes(size)}
</div>
<div className={styles.quality}>
<EpisodeQuality
quality={quality}
isCutoffNotMet={qualityCutoffNotMet}
/>
</div>
<div className={styles.actions}>
<IconButton
name={icons.REMOVE}
onPress={this.onRemoveEpisodeFilePress}
/>
</div>
</div>
<div className={styles.noEpisodes}>
No tracks in this group
</div>
}
</div>
}
}
</div>
<ConfirmModal
isOpen={this.state.isRemoveEpisodeFileModalOpen}
@ -155,6 +143,8 @@ EpisodeSummary.propTypes = {
overview: PropTypes.string,
albumLabel: PropTypes.arrayOf(PropTypes.string),
releaseDate: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
path: PropTypes.string,
size: PropTypes.number,
quality: PropTypes.object,

View file

@ -2,42 +2,28 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteEpisodeFile } from 'Store/Actions/episodeFileActions';
import createEpisodeSelector from 'Store/Selectors/createEpisodeSelector';
import createTrackSelector from 'Store/Selectors/createTrackSelector';
import createEpisodeFileSelector from 'Store/Selectors/createEpisodeFileSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import EpisodeSummary from './EpisodeSummary';
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
(state, { episode }) => episode,
(state) => state.tracks,
createEpisodeSelector(),
createEpisodeFileSelector(),
(series, episode, episodeFile) => {
const {
qualityProfileId,
network
} = series;
const {
airDateUtc,
overview
} = episode;
const {
path,
size,
quality,
qualityCutoffNotMet
} = episodeFile || {};
createCommandsSelector(),
createDimensionsSelector(),
(albumId, tracks, episode, commands, dimensions) => {
return {
network,
qualityProfileId,
airDateUtc,
overview,
path,
size,
quality,
qualityCutoffNotMet
network: episode.label,
qualityProfileId: episode.profileId,
airDateUtc: episode.releaseDate,
overview: episode.overview,
items: tracks.items,
columns: tracks.columns
};
}
);

View file

@ -0,0 +1,26 @@
.title {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
white-space: nowrap;
}
.monitored {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 42px;
}
.trackNumber {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 50px;
}
.language,
.audio,
.video,
.status {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 100px;
}

View file

@ -0,0 +1,123 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import MediaInfoConnector from 'EpisodeFile/MediaInfoConnector';
import * as mediaInfoTypes from 'EpisodeFile/mediaInfoTypes';
import EpisodeStatusConnector from 'Episode/EpisodeStatusConnector';
import styles from './TrackDetailRow.css';
class TrackDetailRow extends Component {
//
// Lifecycle
//
// Listeners
//
// Render
render() {
const {
id,
title,
trackNumber,
duration,
columns,
trackFileId
} = this.props;
return (
<TableRow>
{
columns.map((column) => {
const {
name,
isVisible
} = column;
if (!isVisible) {
return null;
}
if (name === 'trackNumber') {
return (
<TableRowCell
key={name}
className={styles.trackNumber}
>
{trackNumber}
</TableRowCell>
);
}
if (name === 'title') {
return (
<TableRowCell
key={name}
className={styles.title}
>
{title}
</TableRowCell>
);
}
if (name === 'duration') {
return (
<TableRowCell key={name}>
{
formatTimeSpan(duration)
}
</TableRowCell>
);
}
if (name === 'audioInfo') {
return (
<TableRowCell
key={name}
className={styles.audio}
>
<MediaInfoConnector
type={mediaInfoTypes.AUDIO}
episodeFileId={trackFileId}
/>
</TableRowCell>
);
}
if (name === 'status') {
return (
<TableRowCell
key={name}
className={styles.status}
>
<EpisodeStatusConnector
episodeId={id}
episodeFileId={trackFileId}
/>
</TableRowCell>
);
}
return null;
})
}
</TableRow>
);
}
}
TrackDetailRow.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
duration: PropTypes.number.isRequired,
trackFileId: PropTypes.number.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
trackNumber: PropTypes.number.isRequired
};
export default TrackDetailRow;

View file

@ -18,7 +18,7 @@ export const defaultState = {
columns: [
{
name: 'trackNumber',
label: 'Track Number',
label: '#',
isVisible: true
},
{
@ -31,6 +31,16 @@ export const defaultState = {
label: 'Duration',
isVisible: true
},
{
name: 'audioInfo',
label: 'Audio Info',
isVisible: true
},
{
name: 'status',
label: 'Status',
isVisible: true
},
{
name: 'actions',
columnLabel: 'Actions',