mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-11 15:47:09 -07:00
Add Tracklist to Album Modal
This commit is contained in:
parent
90d9741056
commit
d243a8c8c4
16 changed files with 283 additions and 104 deletions
|
@ -188,7 +188,7 @@ EpisodeDetailsModalContent.propTypes = {
|
|||
|
||||
EpisodeDetailsModalContent.defaultProps = {
|
||||
selectedTab: 'details',
|
||||
albumLabel: 'Unknown',
|
||||
albumLabel: ['Unknown'],
|
||||
episodeEntity: episodeEntities.EPISODES,
|
||||
startInteractiveSearch: false
|
||||
};
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
26
frontend/src/Episode/Summary/TrackDetailRow.css
Normal file
26
frontend/src/Episode/Summary/TrackDetailRow.css
Normal 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;
|
||||
}
|
123
frontend/src/Episode/Summary/TrackDetailRow.js
Normal file
123
frontend/src/Episode/Summary/TrackDetailRow.js
Normal 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;
|
|
@ -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',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue