mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-14 02:37:08 -07:00
New: Update DB to store all releases for an album (#517)
* New: Store all releases for an album and track artists * Add Overview, links and release date by release * Tidy up * Fix metadata refresh errors following musicbrainz edits
This commit is contained in:
parent
24bdb5a891
commit
c392569a63
136 changed files with 2305 additions and 1120 deletions
|
@ -106,6 +106,7 @@
|
|||
|
||||
.sizeOnDisk,
|
||||
.qualityProfileName,
|
||||
.links,
|
||||
.tags {
|
||||
margin-left: 8px;
|
||||
font-weight: 300;
|
||||
|
|
|
@ -2,14 +2,17 @@ import _ from 'lodash';
|
|||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TextTruncate from 'react-text-truncate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import { align, icons, sizes } from 'Helpers/Props';
|
||||
import { align, icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||
import fonts from 'Styles/Variables/fonts';
|
||||
import HeartRating from 'Components/HeartRating';
|
||||
import Icon from 'Components/Icon';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Label from 'Components/Label';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import AlbumCover from 'Album/AlbumCover';
|
||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||
import EditAlbumModalConnector from 'Album/Edit/EditAlbumModalConnector';
|
||||
|
@ -24,9 +27,12 @@ import AlbumDetailsMediumConnector from './AlbumDetailsMediumConnector';
|
|||
import ArtistHistoryModal from 'Artist/History/ArtistHistoryModal';
|
||||
import InteractiveSearchModal from 'InteractiveSearch/InteractiveSearchModal';
|
||||
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
|
||||
|
||||
import AlbumDetailsLinks from './AlbumDetailsLinks';
|
||||
import styles from './AlbumDetails.css';
|
||||
|
||||
const defaultFontSize = parseInt(fonts.defaultFontSize);
|
||||
const lineHeight = parseFloat(fonts.lineHeight);
|
||||
|
||||
function getFanartUrl(images) {
|
||||
const fanartImage = _.find(images, { coverType: 'fanart' });
|
||||
if (fanartImage) {
|
||||
|
@ -135,14 +141,17 @@ class AlbumDetails extends Component {
|
|||
render() {
|
||||
const {
|
||||
id,
|
||||
foreignAlbumId,
|
||||
title,
|
||||
disambiguation,
|
||||
overview,
|
||||
albumType,
|
||||
statistics = {},
|
||||
monitored,
|
||||
releaseDate,
|
||||
ratings,
|
||||
images,
|
||||
links,
|
||||
media,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
|
@ -357,6 +366,38 @@ class AlbumDetails extends Component {
|
|||
</Label>
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
anchor={
|
||||
<Label
|
||||
className={styles.detailsLabel}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<Icon
|
||||
name={icons.EXTERNAL_LINK}
|
||||
size={17}
|
||||
/>
|
||||
|
||||
<span className={styles.links}>
|
||||
Links
|
||||
</span>
|
||||
</Label>
|
||||
}
|
||||
tooltip={
|
||||
<AlbumDetailsLinks
|
||||
foreignAlbumId={foreignAlbumId}
|
||||
links={links}
|
||||
/>
|
||||
}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className={styles.overview}>
|
||||
<TextTruncate
|
||||
line={Math.floor(125 / (defaultFontSize * lineHeight))}
|
||||
text={overview}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -446,11 +487,13 @@ AlbumDetails.propTypes = {
|
|||
foreignAlbumId: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
disambiguation: PropTypes.string,
|
||||
overview: PropTypes.string,
|
||||
albumType: PropTypes.string.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
media: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
|
|
|
@ -67,6 +67,10 @@ const mapDispatchToProps = {
|
|||
clearTrackFiles
|
||||
};
|
||||
|
||||
function getMonitoredReleases(props) {
|
||||
return _.map(_.filter(props.releases, { monitored: true }), 'id').sort();
|
||||
}
|
||||
|
||||
class AlbumDetailsConnector extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -75,14 +79,8 @@ class AlbumDetailsConnector extends Component {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
id
|
||||
} = this.props;
|
||||
|
||||
// If the id has changed we need to clear the tracks/track
|
||||
// files and fetch from the server.
|
||||
|
||||
if (prevProps.id !== id) {
|
||||
if (!_.isEqual(getMonitoredReleases(prevProps), getMonitoredReleases(this.props)) ||
|
||||
(prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) {
|
||||
this.unpopulate();
|
||||
this.populate();
|
||||
}
|
||||
|
|
13
frontend/src/Album/Details/AlbumDetailsLinks.css
Normal file
13
frontend/src/Album/Details/AlbumDetailsLinks.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
.links {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.link {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.linkLabel {
|
||||
composes: label from 'Components/Label.css';
|
||||
|
||||
cursor: pointer;
|
||||
}
|
63
frontend/src/Album/Details/AlbumDetailsLinks.js
Normal file
63
frontend/src/Album/Details/AlbumDetailsLinks.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
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 './AlbumDetailsLinks.css';
|
||||
|
||||
function AlbumDetailsLinks(props) {
|
||||
const {
|
||||
foreignAlbumId,
|
||||
links
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={styles.links}>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`https://musicbrainz.org/release-group/${foreignAlbumId}`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Musicbrainz
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
{links.map((link, index) => {
|
||||
return (
|
||||
<span key={index}>
|
||||
<Link className={styles.link}
|
||||
to={link.url}
|
||||
key={index}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{link.name}
|
||||
</Label>
|
||||
</Link>
|
||||
{(index > 0 && index % 5 === 0) &&
|
||||
<br />
|
||||
}
|
||||
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
AlbumDetailsLinks.propTypes = {
|
||||
foreignAlbumId: PropTypes.string.isRequired,
|
||||
links: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
export default AlbumDetailsLinks;
|
|
@ -19,7 +19,7 @@ function getMediumStatistics(tracks) {
|
|||
if (track.trackFileId) {
|
||||
trackCount++;
|
||||
trackFileCount++;
|
||||
} else if (track.monitored) {
|
||||
} else {
|
||||
trackCount++;
|
||||
}
|
||||
|
||||
|
|
|
@ -178,7 +178,6 @@ TrackRow.propTypes = {
|
|||
id: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.number.isRequired,
|
||||
trackFileId: PropTypes.number,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
mediumNumber: PropTypes.number.isRequired,
|
||||
trackNumber: PropTypes.string.isRequired,
|
||||
absoluteTrackNumber: PropTypes.number,
|
||||
|
|
|
@ -43,7 +43,7 @@ class EditAlbumModalContent extends Component {
|
|||
|
||||
const {
|
||||
monitored,
|
||||
currentRelease,
|
||||
anyReleaseOk,
|
||||
releases
|
||||
} = item;
|
||||
|
||||
|
@ -69,15 +69,26 @@ class EditAlbumModalContent extends Component {
|
|||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Automatically Switch Release</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="anyReleaseOk"
|
||||
helpText="Lidarr will automatically switch to the release best matching downloaded tracks"
|
||||
{...anyReleaseOk}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel> Release</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.ALBUM_RELEASE_SELECT}
|
||||
name="currentRelease"
|
||||
name="releases"
|
||||
helpText="Change release for this album"
|
||||
albumReleases={releases}
|
||||
selectedRelease={currentRelease}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
|
|
@ -23,7 +23,7 @@ function createMapStateToProps() {
|
|||
|
||||
const albumSettings = _.pick(album, [
|
||||
'monitored',
|
||||
'currentRelease',
|
||||
'anyReleaseOk',
|
||||
'releases'
|
||||
]);
|
||||
|
||||
|
|
|
@ -344,6 +344,14 @@ class ArtistDetails extends Component {
|
|||
to={`/artist/${previousArtist.foreignArtistId}`}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={styles.artistNavigationButton}
|
||||
name={icons.ARROW_UP}
|
||||
size={30}
|
||||
title={'Go to artist listing'}
|
||||
to={'/'}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={styles.artistNavigationButton}
|
||||
name={icons.ARROW_RIGHT}
|
||||
|
|
|
@ -9,15 +9,14 @@ import SelectInput from './SelectInput';
|
|||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { albumReleases }) => albumReleases,
|
||||
(state, { selectedRelease }) => selectedRelease,
|
||||
(albumReleases, selectedRelease) => {
|
||||
(albumReleases) => {
|
||||
const values = _.map(albumReleases.value, (albumRelease) => {
|
||||
|
||||
return {
|
||||
key: albumRelease.id,
|
||||
key: albumRelease.foreignReleaseId,
|
||||
value: `${albumRelease.title}` +
|
||||
`${albumRelease.disambiguation ? ' (' : ''}${titleCase(albumRelease.disambiguation)}${albumRelease.disambiguation ? ')' : ''}` +
|
||||
`, ${albumRelease.mediaCount} med, ${albumRelease.trackCount} tracks` +
|
||||
`, ${albumRelease.mediumCount} med, ${albumRelease.trackCount} tracks` +
|
||||
`${albumRelease.country.length > 0 ? ', ' : ''}${albumRelease.country}` +
|
||||
`${albumRelease.format ? ', [' : ''}${albumRelease.format}${albumRelease.format ? ']' : ''}`
|
||||
};
|
||||
|
@ -25,7 +24,7 @@ function createMapStateToProps() {
|
|||
|
||||
const sortedValues = _.orderBy(values, ['value']);
|
||||
|
||||
const value = selectedRelease.value.id;
|
||||
const value = _.find(albumReleases.value, { monitored: true }).foreignReleaseId;
|
||||
|
||||
return {
|
||||
values: sortedValues,
|
||||
|
@ -45,7 +44,10 @@ class AlbumReleaseSelectInputConnector extends Component {
|
|||
albumReleases
|
||||
} = this.props;
|
||||
|
||||
this.props.onChange({ name, value: _.find(albumReleases.value, { id: value }) });
|
||||
let updatedReleases = _.map(albumReleases.value, (e) => ({ ...e, monitored: false }));
|
||||
_.find(updatedReleases, { foreignReleaseId: value }).monitored = true;
|
||||
|
||||
this.props.onChange({ name, value: updatedReleases });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -105,6 +105,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||
const {
|
||||
artist,
|
||||
album,
|
||||
albumReleaseId,
|
||||
tracks,
|
||||
quality,
|
||||
language
|
||||
|
@ -140,6 +141,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||
folderName: item.folderName,
|
||||
artistId: artist.id,
|
||||
albumId: album.id,
|
||||
albumReleaseId,
|
||||
trackIds: _.map(tracks, 'id'),
|
||||
quality,
|
||||
language,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue