New: Show downloading status for artist progress bar

(cherry picked from commit ac806a2933bf2dc0c96d471ec143fca8e1f5282f)
This commit is contained in:
Mark McDowall 2023-03-12 23:51:30 -07:00 committed by Bogdan
parent f1dede240d
commit af12fad185
14 changed files with 132 additions and 29 deletions

View file

@ -1,4 +1,10 @@
import React, { useCallback, useMemo, useRef, useState } from 'react'; import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { SelectProvider } from 'App/SelectContext'; import { SelectProvider } from 'App/SelectContext';
import NoArtist from 'Artist/NoArtist'; import NoArtist from 'Artist/NoArtist';
@ -22,6 +28,7 @@ import {
setArtistView, setArtistView,
} from 'Store/Actions/artistIndexActions'; } from 'Store/Actions/artistIndexActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import { fetchQueueDetails } from 'Store/Actions/queueActions';
import scrollPositions from 'Store/scrollPositions'; import scrollPositions from 'Store/scrollPositions';
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector'; import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
@ -48,7 +55,7 @@ import ArtistIndexTable from './Table/ArtistIndexTable';
import ArtistIndexTableOptions from './Table/ArtistIndexTableOptions'; import ArtistIndexTableOptions from './Table/ArtistIndexTableOptions';
import styles from './ArtistIndex.css'; import styles from './ArtistIndex.css';
function getViewComponent(view) { function getViewComponent(view: string) {
if (view === 'posters') { if (view === 'posters') {
return ArtistIndexPosters; return ArtistIndexPosters;
} }
@ -94,6 +101,10 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null); const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
const [isSelectMode, setIsSelectMode] = useState(false); const [isSelectMode, setIsSelectMode] = useState(false);
useEffect(() => {
dispatch(fetchQueueDetails({ all: true }));
}, [dispatch]);
const onRssSyncPress = useCallback(() => { const onRssSyncPress = useCallback(() => {
dispatch( dispatch(
executeCommand({ executeCommand({

View file

@ -50,6 +50,12 @@
} }
} }
.downloading {
composes: legendItemColor;
background-color: var(--purple);
}
.statistics { .statistics {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View file

@ -2,6 +2,7 @@
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'continuing': string; 'continuing': string;
'downloading': string;
'ended': string; 'ended': string;
'footer': string; 'footer': string;
'legendItem': string; 'legendItem': string;

View file

@ -113,6 +113,16 @@ export default function ArtistIndexFooter() {
/> />
<div>{translate('MissingTracksArtistNotMonitored')}</div> <div>{translate('MissingTracksArtistNotMonitored')}</div>
</div> </div>
<div className={styles.legendItem}>
<div
className={classNames(
styles.downloading,
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>{translate('ArtistIndexFooterDownloading')}</div>
</div>
</div> </div>
<div className={styles.statistics}> <div className={styles.statistics}>

View file

@ -183,13 +183,15 @@ function ArtistIndexBanner(props: ArtistIndexBannerProps) {
</div> </div>
<ArtistIndexProgressBar <ArtistIndexProgressBar
artistId={artistId}
monitored={monitored} monitored={monitored}
status={status} status={status}
trackCount={trackCount} trackCount={trackCount}
trackFileCount={trackFileCount} trackFileCount={trackFileCount}
totalTrackCount={totalTrackCount} totalTrackCount={totalTrackCount}
posterWidth={bannerWidth} width={bannerWidth}
detailedProgressBar={detailedProgressBar} detailedProgressBar={detailedProgressBar}
isStandalone={false}
/> />
{showTitle ? ( {showTitle ? (

View file

@ -160,13 +160,15 @@ function ArtistIndexOverview(props: ArtistIndexOverviewProps) {
</div> </div>
<ArtistIndexProgressBar <ArtistIndexProgressBar
artistId={artistId}
monitored={monitored} monitored={monitored}
status={status} status={status}
trackCount={trackCount} trackCount={trackCount}
trackFileCount={trackFileCount} trackFileCount={trackFileCount}
totalTrackCount={totalTrackCount} totalTrackCount={totalTrackCount}
posterWidth={posterWidth} width={posterWidth}
detailedProgressBar={overviewOptions.detailedProgressBar} detailedProgressBar={overviewOptions.detailedProgressBar}
isStandalone={false}
/> />
</div> </div>

View file

@ -183,13 +183,15 @@ function ArtistIndexPoster(props: ArtistIndexPosterProps) {
</div> </div>
<ArtistIndexProgressBar <ArtistIndexProgressBar
artistId={artistId}
monitored={monitored} monitored={monitored}
status={status} status={status}
trackCount={trackCount} trackCount={trackCount}
trackFileCount={trackFileCount} trackFileCount={trackFileCount}
totalTrackCount={totalTrackCount} totalTrackCount={totalTrackCount}
posterWidth={posterWidth} width={posterWidth}
detailedProgressBar={detailedProgressBar} detailedProgressBar={detailedProgressBar}
isStandalone={false}
/> />
{showTitle ? ( {showTitle ? (

View file

@ -1,4 +1,8 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux';
import createArtistQueueItemsDetailsSelector, {
ArtistQueueDetails,
} from 'Artist/Index/createArtistQueueDetailsSelector';
import ProgressBar from 'Components/ProgressBar'; import ProgressBar from 'Components/ProgressBar';
import { sizes } from 'Helpers/Props'; import { sizes } from 'Helpers/Props';
import getProgressBarKind from 'Utilities/Artist/getProgressBarKind'; import getProgressBarKind from 'Utilities/Artist/getProgressBarKind';
@ -6,35 +10,51 @@ import translate from 'Utilities/String/translate';
import styles from './ArtistIndexProgressBar.css'; import styles from './ArtistIndexProgressBar.css';
interface ArtistIndexProgressBarProps { interface ArtistIndexProgressBarProps {
artistId: number;
monitored: boolean; monitored: boolean;
status: string; status: string;
trackCount: number; trackCount: number;
trackFileCount: number; trackFileCount: number;
totalTrackCount: number; totalTrackCount: number;
posterWidth: number; width: number;
detailedProgressBar: boolean; detailedProgressBar: boolean;
isStandalone: boolean;
} }
function ArtistIndexProgressBar(props: ArtistIndexProgressBarProps) { function ArtistIndexProgressBar(props: ArtistIndexProgressBarProps) {
const { const {
artistId,
monitored, monitored,
status, status,
trackCount, trackCount,
trackFileCount, trackFileCount,
totalTrackCount, totalTrackCount,
posterWidth, width,
detailedProgressBar, detailedProgressBar,
isStandalone,
} = props; } = props;
const queueDetails: ArtistQueueDetails = useSelector(
createArtistQueueItemsDetailsSelector(artistId)
);
const newDownloads = queueDetails.count - queueDetails.tracksWithFiles;
const progress = trackCount ? (trackFileCount / trackCount) * 100 : 100; const progress = trackCount ? (trackFileCount / trackCount) * 100 : 100;
const text = `${trackFileCount} / ${trackCount}`; const text = newDownloads
? `${trackFileCount} + ${newDownloads} / ${trackCount}`
: `${trackFileCount} / ${trackCount}`;
return ( return (
<ProgressBar <ProgressBar
className={styles.progressBar} className={styles.progressBar}
containerClassName={styles.progress} containerClassName={isStandalone ? undefined : styles.progress}
progress={progress} progress={progress}
kind={getProgressBarKind(status, monitored, progress)} kind={getProgressBarKind(
status,
monitored,
progress,
queueDetails.count > 0
)}
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL} size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
showText={detailedProgressBar} showText={detailedProgressBar}
text={text} text={text}
@ -42,8 +62,9 @@ function ArtistIndexProgressBar(props: ArtistIndexProgressBarProps) {
trackFileCount, trackFileCount,
trackCount, trackCount,
totalTrackCount, totalTrackCount,
downloadingCount: queueDetails.count,
})} })}
width={posterWidth} width={width}
/> />
); );
} }

View file

@ -9,13 +9,13 @@ import ArtistNameLink from 'Artist/ArtistNameLink';
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal'; import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector'; import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
import createArtistIndexItemSelector from 'Artist/Index/createArtistIndexItemSelector'; import createArtistIndexItemSelector from 'Artist/Index/createArtistIndexItemSelector';
import ArtistIndexProgressBar from 'Artist/Index/ProgressBar/ArtistIndexProgressBar';
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell'; import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell';
import { ARTIST_SEARCH, REFRESH_ARTIST } from 'Commands/commandNames'; import { ARTIST_SEARCH, REFRESH_ARTIST } from 'Commands/commandNames';
import HeartRating from 'Components/HeartRating'; import HeartRating from 'Components/HeartRating';
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 SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import ProgressBar from 'Components/ProgressBar';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell'; import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
@ -23,7 +23,6 @@ import Column from 'Components/Table/Column';
import TagListConnector from 'Components/TagListConnector'; import TagListConnector from 'Components/TagListConnector';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import getProgressBarKind from 'Utilities/Artist/getProgressBarKind';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import AlbumsCell from './AlbumsCell'; import AlbumsCell from './AlbumsCell';
@ -301,23 +300,18 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
} }
if (name === 'trackProgress') { if (name === 'trackProgress') {
const progress = trackCount
? (trackFileCount / trackCount) * 100
: 100;
return ( return (
<VirtualTableRowCell key={name} className={styles[name]}> <VirtualTableRowCell key={name} className={styles[name]}>
<ProgressBar <ArtistIndexProgressBar
progress={progress} artistId={artistId}
kind={getProgressBarKind(status, monitored, progress)} monitored={monitored}
showText={true} status={status}
text={`${trackFileCount} / ${trackCount}`} trackCount={trackCount}
title={translate('ArtistProgressBarText', { trackFileCount={trackFileCount}
trackFileCount, totalTrackCount={totalTrackCount}
trackCount,
totalTrackCount,
})}
width={125} width={125}
detailedProgressBar={true}
isStandalone={true}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
); );

View file

@ -0,0 +1,36 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
export interface ArtistQueueDetails {
count: number;
tracksWithFiles: number;
}
function createArtistQueueDetailsSelector(artistId: number) {
return createSelector(
(state: AppState) => state.queue.details.items,
(queueItems) => {
return queueItems.reduce(
(acc: ArtistQueueDetails, item) => {
if (item.artistId !== artistId) {
return acc;
}
acc.count += item.trackFileCount ?? 0;
if (item.trackHasFileCount) {
acc.tracksWithFiles += item.trackHasFileCount;
}
return acc;
},
{
count: 0,
tracksWithFiles: 0,
}
);
}
);
}
export default createArtistQueueDetailsSelector;

View file

@ -1,6 +1,15 @@
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
function getProgressBarKind(status, monitored, progress) { function getProgressBarKind(
status: string,
monitored: boolean,
progress: number,
isDownloading: boolean
) {
if (isDownloading) {
return kinds.PURPLE;
}
if (progress === 100) { if (progress === 100) {
return status === 'ended' ? kinds.SUCCESS : kinds.PRIMARY; return status === 'ended' ? kinds.SUCCESS : kinds.PRIMARY;
} }

View file

@ -25,6 +25,8 @@ interface Queue extends ModelBase {
protocol: string; protocol: string;
downloadClient: string; downloadClient: string;
outputPath: string; outputPath: string;
trackFileCount: number;
trackHasFileCount: number;
artistId?: number; artistId?: number;
albumId?: number; albumId?: number;
} }

View file

@ -38,6 +38,8 @@ namespace Lidarr.Api.V1.Queue
public bool DownloadClientHasPostImportCategory { get; set; } public bool DownloadClientHasPostImportCategory { get; set; }
public string Indexer { get; set; } public string Indexer { get; set; }
public string OutputPath { get; set; } public string OutputPath { get; set; }
public int TrackFileCount { get; set; }
public int TrackHasFileCount { get; set; }
public bool DownloadForced { get; set; } public bool DownloadForced { get; set; }
} }
@ -53,6 +55,8 @@ namespace Lidarr.Api.V1.Queue
var customFormats = model.RemoteAlbum?.CustomFormats; var customFormats = model.RemoteAlbum?.CustomFormats;
var customFormatScore = model.Artist?.QualityProfile?.Value?.CalculateCustomFormatScore(customFormats) ?? 0; var customFormatScore = model.Artist?.QualityProfile?.Value?.CalculateCustomFormatScore(customFormats) ?? 0;
var albumRelease = model.Album?.AlbumReleases?.Value?.SingleOrDefault(x => x.Monitored);
return new QueueResource return new QueueResource
{ {
Id = model.Id, Id = model.Id,
@ -80,6 +84,8 @@ namespace Lidarr.Api.V1.Queue
DownloadClientHasPostImportCategory = model.DownloadClientHasPostImportCategory, DownloadClientHasPostImportCategory = model.DownloadClientHasPostImportCategory,
Indexer = model.Indexer, Indexer = model.Indexer,
OutputPath = model.OutputPath, OutputPath = model.OutputPath,
TrackFileCount = albumRelease?.Tracks?.Value?.Count ?? 0,
TrackHasFileCount = albumRelease?.Tracks?.Value?.Count(x => x.HasFile) ?? 0,
DownloadForced = model.DownloadForced DownloadForced = model.DownloadForced
}; };
} }

View file

@ -111,12 +111,13 @@
"ArtistClickToChangeAlbum": "Click to change album", "ArtistClickToChangeAlbum": "Click to change album",
"ArtistEditor": "Artist Editor", "ArtistEditor": "Artist Editor",
"ArtistFolderFormat": "Artist Folder Format", "ArtistFolderFormat": "Artist Folder Format",
"ArtistIndexFooterDownloading": "Downloading",
"ArtistIsMonitored": "Artist is monitored", "ArtistIsMonitored": "Artist is monitored",
"ArtistIsUnmonitored": "Artist is unmonitored", "ArtistIsUnmonitored": "Artist is unmonitored",
"ArtistMonitoring": "Artist Monitoring", "ArtistMonitoring": "Artist Monitoring",
"ArtistName": "Artist Name", "ArtistName": "Artist Name",
"ArtistNameHelpText": "The name of the artist/album to exclude (can be anything meaningful)", "ArtistNameHelpText": "The name of the artist/album to exclude (can be anything meaningful)",
"ArtistProgressBarText": "{trackFileCount} / {trackCount} (Total: {totalTrackCount})", "ArtistProgressBarText": "{trackFileCount} / {trackCount} (Total: {totalTrackCount}, Downloading: {downloadingCount})",
"ArtistType": "Artist Type", "ArtistType": "Artist Type",
"Artists": "Artists", "Artists": "Artists",
"ArtistsEditRootFolderHelpText": "Moving artists to the same root folder can be used to rename artist folders to match updated name or naming format", "ArtistsEditRootFolderHelpText": "Moving artists to the same root folder can be used to rename artist folders to match updated name or naming format",