mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-15 01:23:53 -07:00
New: Album group improvements on Artist Details page
This commit is contained in:
parent
7e10d6b59c
commit
4b367d3129
5 changed files with 205 additions and 28 deletions
11
frontend/src/Artist/Details/AlbumGroupInfo.css
Normal file
11
frontend/src/Artist/Details/AlbumGroupInfo.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.title {
|
||||||
|
composes: title from '~Components/DescriptionList/DescriptionListItemTitle.css';
|
||||||
|
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
composes: title from '~Components/DescriptionList/DescriptionListItemDescription.css';
|
||||||
|
|
||||||
|
margin-left: 110px;
|
||||||
|
}
|
56
frontend/src/Artist/Details/AlbumGroupInfo.js
Normal file
56
frontend/src/Artist/Details/AlbumGroupInfo.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||||
|
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||||
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
import styles from './AlbumGroupInfo.css';
|
||||||
|
|
||||||
|
function AlbumGroupInfo(props) {
|
||||||
|
const {
|
||||||
|
totalAlbumCount,
|
||||||
|
monitoredAlbumCount,
|
||||||
|
trackFileCount,
|
||||||
|
sizeOnDisk
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DescriptionList>
|
||||||
|
<DescriptionListItem
|
||||||
|
titleClassName={styles.title}
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title="Total"
|
||||||
|
data={totalAlbumCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DescriptionListItem
|
||||||
|
titleClassName={styles.title}
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title="Monitored"
|
||||||
|
data={monitoredAlbumCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DescriptionListItem
|
||||||
|
titleClassName={styles.title}
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title="Track Files"
|
||||||
|
data={trackFileCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DescriptionListItem
|
||||||
|
titleClassName={styles.title}
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title="Size on Disk"
|
||||||
|
data={formatBytes(sizeOnDisk)}
|
||||||
|
/>
|
||||||
|
</DescriptionList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AlbumGroupInfo.propTypes = {
|
||||||
|
totalAlbumCount: PropTypes.number.isRequired,
|
||||||
|
monitoredAlbumCount: PropTypes.number.isRequired,
|
||||||
|
trackFileCount: PropTypes.number.isRequired,
|
||||||
|
sizeOnDisk: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AlbumGroupInfo;
|
|
@ -15,11 +15,10 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.albumTypeLabel {
|
.albumTypeLabel {
|
||||||
margin-right: 5px;
|
margin-right: 10px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,10 +28,16 @@
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.episodeCountTooltip {
|
.albumCountTooltip {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sizeOnDisk {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #777;
|
||||||
|
font-size: $defaultFontSize;
|
||||||
|
}
|
||||||
|
|
||||||
.expandButton {
|
.expandButton {
|
||||||
composes: link from '~Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
|
@ -44,7 +49,7 @@
|
||||||
.left {
|
.left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 0 1 300px;
|
flex: 0 1 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left,
|
.left,
|
||||||
|
@ -103,7 +108,7 @@
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -12px;
|
margin-top: -12px;
|
||||||
margin-left: -15px;
|
margin-left: -12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noAlbums {
|
.noAlbums {
|
||||||
|
@ -122,4 +127,8 @@
|
||||||
position: static;
|
position: static;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sizeOnDisk {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,70 @@ import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
|
import Label from 'Components/Label';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { icons, sortDirections } from 'Helpers/Props';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
|
import { icons, kinds, sizes, sortDirections, tooltipPositions } from 'Helpers/Props';
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
|
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
|
||||||
|
import isBefore from 'Utilities/Date/isBefore';
|
||||||
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import getToggledRange from 'Utilities/Table/getToggledRange';
|
import getToggledRange from 'Utilities/Table/getToggledRange';
|
||||||
|
import AlbumGroupInfo from './AlbumGroupInfo';
|
||||||
import AlbumRowConnector from './AlbumRowConnector';
|
import AlbumRowConnector from './AlbumRowConnector';
|
||||||
import styles from './ArtistDetailsSeason.css';
|
import styles from './ArtistDetailsSeason.css';
|
||||||
|
|
||||||
|
function getAlbumTypeStatistics(albums) {
|
||||||
|
let albumCount = 0;
|
||||||
|
let trackFileCount = 0;
|
||||||
|
let totalAlbumCount = 0;
|
||||||
|
let monitoredAlbumCount = 0;
|
||||||
|
let hasMonitoredAlbums = false;
|
||||||
|
let sizeOnDisk = 0;
|
||||||
|
|
||||||
|
albums.forEach((album) => {
|
||||||
|
sizeOnDisk = sizeOnDisk + album.statistics.sizeOnDisk;
|
||||||
|
trackFileCount = trackFileCount + album.statistics.trackFileCount;
|
||||||
|
|
||||||
|
if (album.statistics.trackFileCount === album.statistics.totalTrackCount || (album.monitored && isBefore(album.airDateUtc))) {
|
||||||
|
albumCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (album.monitored) {
|
||||||
|
monitoredAlbumCount++;
|
||||||
|
hasMonitoredAlbums = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalAlbumCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
albumCount,
|
||||||
|
totalAlbumCount,
|
||||||
|
trackFileCount,
|
||||||
|
monitoredAlbumCount,
|
||||||
|
sizeOnDisk,
|
||||||
|
hasMonitoredAlbums
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAlbumCountKind(monitored, albumCount, monitoredAlbumCount) {
|
||||||
|
if (albumCount === monitoredAlbumCount && monitoredAlbumCount > 0) {
|
||||||
|
return kinds.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!monitored) {
|
||||||
|
return kinds.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kinds.DANGER;
|
||||||
|
}
|
||||||
|
|
||||||
class ArtistDetailsSeason extends Component {
|
class ArtistDetailsSeason extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -108,7 +160,13 @@ class ArtistDetailsSeason extends Component {
|
||||||
|
|
||||||
this.setState({ lastToggledAlbum: albumId });
|
this.setState({ lastToggledAlbum: albumId });
|
||||||
|
|
||||||
this.props.onMonitorAlbumPress(_.uniq(albumIds), monitored);
|
this.props.onMonitorAlbumsPress(_.uniq(albumIds), monitored);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMonitorAlbumsPress = (monitored, { shiftKey }) => {
|
||||||
|
const albumIds = this.props.items.map((a) => a.id);
|
||||||
|
|
||||||
|
this.props.onMonitorAlbumsPress(_.uniq(albumIds), monitored);
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -120,7 +178,9 @@ class ArtistDetailsSeason extends Component {
|
||||||
label,
|
label,
|
||||||
items,
|
items,
|
||||||
columns,
|
columns,
|
||||||
|
isSaving,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
|
artistMonitored,
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
|
@ -128,6 +188,15 @@ class ArtistDetailsSeason extends Component {
|
||||||
onTableOptionChange
|
onTableOptionChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
albumCount,
|
||||||
|
totalAlbumCount,
|
||||||
|
trackFileCount,
|
||||||
|
monitoredAlbumCount,
|
||||||
|
sizeOnDisk,
|
||||||
|
hasMonitoredAlbums
|
||||||
|
} = getAlbumTypeStatistics(items);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isManageTracksOpen
|
isManageTracksOpen
|
||||||
|
@ -137,25 +206,55 @@ class ArtistDetailsSeason extends Component {
|
||||||
<div
|
<div
|
||||||
className={styles.albumType}
|
className={styles.albumType}
|
||||||
>
|
>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div className={styles.left}>
|
||||||
|
<MonitorToggleButton
|
||||||
|
monitored={hasMonitoredAlbums}
|
||||||
|
isDisabled={!artistMonitored}
|
||||||
|
isSaving={isSaving}
|
||||||
|
size={24}
|
||||||
|
onPress={this.onMonitorAlbumsPress}
|
||||||
|
/>
|
||||||
|
<span className={styles.albumTypeLabel}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<Popover
|
||||||
|
className={styles.albumCountTooltip}
|
||||||
|
anchor={
|
||||||
|
<Label
|
||||||
|
size={sizes.LARGE}
|
||||||
|
kind={getAlbumCountKind(hasMonitoredAlbums, albumCount, monitoredAlbumCount)}
|
||||||
|
>
|
||||||
|
<span>{albumCount} / {monitoredAlbumCount}</span>
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
title="Group Information"
|
||||||
|
body={
|
||||||
|
<div>
|
||||||
|
<AlbumGroupInfo
|
||||||
|
totalAlbumCount={totalAlbumCount}
|
||||||
|
monitoredAlbumCount={monitoredAlbumCount}
|
||||||
|
trackFileCount={trackFileCount}
|
||||||
|
sizeOnDisk={sizeOnDisk}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position={tooltipPositions.BOTTOM}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
sizeOnDisk ?
|
||||||
|
<div className={styles.sizeOnDisk}>
|
||||||
|
{formatBytes(sizeOnDisk)}
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
className={styles.expandButton}
|
className={styles.expandButton}
|
||||||
onPress={this.onExpandPress}
|
onPress={this.onExpandPress}
|
||||||
>
|
>
|
||||||
<div className={styles.header}>
|
|
||||||
<div className={styles.left}>
|
|
||||||
{
|
|
||||||
<div>
|
|
||||||
<span className={styles.albumTypeLabel}>
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className={styles.albumCount}>
|
|
||||||
({items.length} Releases)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.expandButtonIcon}
|
className={styles.expandButtonIcon}
|
||||||
|
@ -168,9 +267,9 @@ class ArtistDetailsSeason extends Component {
|
||||||
!isSmallScreen &&
|
!isSmallScreen &&
|
||||||
<span> </span>
|
<span> </span>
|
||||||
}
|
}
|
||||||
|
</Link>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
|
@ -238,16 +337,18 @@ ArtistDetailsSeason.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
artistId: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
|
artistMonitored: PropTypes.bool.isRequired,
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
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,
|
||||||
isExpanded: PropTypes.bool,
|
isExpanded: PropTypes.bool,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
onTableOptionChange: PropTypes.func.isRequired,
|
||||||
onExpandPress: PropTypes.func.isRequired,
|
onExpandPress: PropTypes.func.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
onMonitorAlbumPress: PropTypes.func.isRequired,
|
onMonitorAlbumsPress: PropTypes.func.isRequired,
|
||||||
uiSettings: PropTypes.object.isRequired
|
uiSettings: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ function createMapStateToProps() {
|
||||||
return {
|
return {
|
||||||
items: sortedAlbums,
|
items: sortedAlbums,
|
||||||
columns: albums.columns,
|
columns: albums.columns,
|
||||||
|
artistMonitored: artist.monitored,
|
||||||
sortKey: albums.sortKey,
|
sortKey: albums.sortKey,
|
||||||
sortDirection: albums.sortDirection,
|
sortDirection: albums.sortDirection,
|
||||||
artistMonitored: artist.monitored,
|
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
uiSettings
|
uiSettings
|
||||||
};
|
};
|
||||||
|
@ -66,7 +66,7 @@ class ArtistDetailsSeasonConnector extends Component {
|
||||||
this.props.dispatchSetAlbumSort({ sortKey });
|
this.props.dispatchSetAlbumSort({ sortKey });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMonitorAlbumPress = (albumIds, monitored) => {
|
onMonitorAlbumsPress = (albumIds, monitored) => {
|
||||||
this.props.toggleAlbumsMonitored({
|
this.props.toggleAlbumsMonitored({
|
||||||
albumIds,
|
albumIds,
|
||||||
monitored
|
monitored
|
||||||
|
@ -82,7 +82,7 @@ class ArtistDetailsSeasonConnector extends Component {
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onSortPress={this.onSortPress}
|
onSortPress={this.onSortPress}
|
||||||
onTableOptionChange={this.onTableOptionChange}
|
onTableOptionChange={this.onTableOptionChange}
|
||||||
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
onMonitorAlbumsPress={this.onMonitorAlbumsPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue