diff --git a/frontend/src/Artist/Index/ArtistIndex.tsx b/frontend/src/Artist/Index/ArtistIndex.tsx index a5c7b8d48..3143ff725 100644 --- a/frontend/src/Artist/Index/ArtistIndex.tsx +++ b/frontend/src/Artist/Index/ArtistIndex.tsx @@ -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 { SelectProvider } from 'App/SelectContext'; import NoArtist from 'Artist/NoArtist'; @@ -22,6 +28,7 @@ import { setArtistView, } from 'Store/Actions/artistIndexActions'; import { executeCommand } from 'Store/Actions/commandActions'; +import { fetchQueueDetails } from 'Store/Actions/queueActions'; import scrollPositions from 'Store/scrollPositions'; import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; @@ -48,7 +55,7 @@ import ArtistIndexTable from './Table/ArtistIndexTable'; import ArtistIndexTableOptions from './Table/ArtistIndexTableOptions'; import styles from './ArtistIndex.css'; -function getViewComponent(view) { +function getViewComponent(view: string) { if (view === 'posters') { return ArtistIndexPosters; } @@ -94,6 +101,10 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => { const [jumpToCharacter, setJumpToCharacter] = useState(null); const [isSelectMode, setIsSelectMode] = useState(false); + useEffect(() => { + dispatch(fetchQueueDetails({ all: true })); + }, [dispatch]); + const onRssSyncPress = useCallback(() => { dispatch( executeCommand({ diff --git a/frontend/src/Artist/Index/ArtistIndexFooter.css b/frontend/src/Artist/Index/ArtistIndexFooter.css index c1c4b5a46..bf3fedfd6 100644 --- a/frontend/src/Artist/Index/ArtistIndexFooter.css +++ b/frontend/src/Artist/Index/ArtistIndexFooter.css @@ -50,6 +50,12 @@ } } +.downloading { + composes: legendItemColor; + + background-color: var(--purple); +} + .statistics { display: flex; justify-content: space-between; diff --git a/frontend/src/Artist/Index/ArtistIndexFooter.css.d.ts b/frontend/src/Artist/Index/ArtistIndexFooter.css.d.ts index b88d23a6c..29f693a8c 100644 --- a/frontend/src/Artist/Index/ArtistIndexFooter.css.d.ts +++ b/frontend/src/Artist/Index/ArtistIndexFooter.css.d.ts @@ -2,6 +2,7 @@ // Please do not change this file! interface CssExports { 'continuing': string; + 'downloading': string; 'ended': string; 'footer': string; 'legendItem': string; diff --git a/frontend/src/Artist/Index/ArtistIndexFooter.tsx b/frontend/src/Artist/Index/ArtistIndexFooter.tsx index 4b4982055..47241b224 100644 --- a/frontend/src/Artist/Index/ArtistIndexFooter.tsx +++ b/frontend/src/Artist/Index/ArtistIndexFooter.tsx @@ -113,6 +113,16 @@ export default function ArtistIndexFooter() { />
{translate('MissingTracksArtistNotMonitored')}
+ +
+
+
{translate('ArtistIndexFooterDownloading')}
+
diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBanner.tsx b/frontend/src/Artist/Index/Banners/ArtistIndexBanner.tsx index d59ea0187..1360d33b2 100644 --- a/frontend/src/Artist/Index/Banners/ArtistIndexBanner.tsx +++ b/frontend/src/Artist/Index/Banners/ArtistIndexBanner.tsx @@ -183,13 +183,15 @@ function ArtistIndexBanner(props: ArtistIndexBannerProps) {
{showTitle ? ( diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverview.tsx b/frontend/src/Artist/Index/Overview/ArtistIndexOverview.tsx index b6a9da738..ebef28264 100644 --- a/frontend/src/Artist/Index/Overview/ArtistIndexOverview.tsx +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverview.tsx @@ -160,13 +160,15 @@ function ArtistIndexOverview(props: ArtistIndexOverviewProps) { diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPoster.tsx b/frontend/src/Artist/Index/Posters/ArtistIndexPoster.tsx index b7733d17f..156368d9b 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPoster.tsx +++ b/frontend/src/Artist/Index/Posters/ArtistIndexPoster.tsx @@ -183,13 +183,15 @@ function ArtistIndexPoster(props: ArtistIndexPosterProps) { {showTitle ? ( diff --git a/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.tsx b/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.tsx index f76fa6588..2a8167b99 100644 --- a/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.tsx +++ b/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import { useSelector } from 'react-redux'; +import createArtistQueueItemsDetailsSelector, { + ArtistQueueDetails, +} from 'Artist/Index/createArtistQueueDetailsSelector'; import ProgressBar from 'Components/ProgressBar'; import { sizes } from 'Helpers/Props'; import getProgressBarKind from 'Utilities/Artist/getProgressBarKind'; @@ -6,35 +10,51 @@ import translate from 'Utilities/String/translate'; import styles from './ArtistIndexProgressBar.css'; interface ArtistIndexProgressBarProps { + artistId: number; monitored: boolean; status: string; trackCount: number; trackFileCount: number; totalTrackCount: number; - posterWidth: number; + width: number; detailedProgressBar: boolean; + isStandalone: boolean; } function ArtistIndexProgressBar(props: ArtistIndexProgressBarProps) { const { + artistId, monitored, status, trackCount, trackFileCount, totalTrackCount, - posterWidth, + width, detailedProgressBar, + isStandalone, } = props; + const queueDetails: ArtistQueueDetails = useSelector( + createArtistQueueItemsDetailsSelector(artistId) + ); + + const newDownloads = queueDetails.count - queueDetails.tracksWithFiles; const progress = trackCount ? (trackFileCount / trackCount) * 100 : 100; - const text = `${trackFileCount} / ${trackCount}`; + const text = newDownloads + ? `${trackFileCount} + ${newDownloads} / ${trackCount}` + : `${trackFileCount} / ${trackCount}`; return ( 0 + )} size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL} showText={detailedProgressBar} text={text} @@ -42,8 +62,9 @@ function ArtistIndexProgressBar(props: ArtistIndexProgressBarProps) { trackFileCount, trackCount, totalTrackCount, + downloadingCount: queueDetails.count, })} - width={posterWidth} + width={width} /> ); } diff --git a/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx b/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx index da277a418..e87545093 100644 --- a/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx +++ b/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx @@ -9,13 +9,13 @@ import ArtistNameLink from 'Artist/ArtistNameLink'; import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal'; import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector'; import createArtistIndexItemSelector from 'Artist/Index/createArtistIndexItemSelector'; +import ArtistIndexProgressBar from 'Artist/Index/ProgressBar/ArtistIndexProgressBar'; import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell'; import { ARTIST_SEARCH, REFRESH_ARTIST } from 'Commands/commandNames'; import HeartRating from 'Components/HeartRating'; import IconButton from 'Components/Link/IconButton'; import Link from 'Components/Link/Link'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; -import ProgressBar from 'Components/ProgressBar'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell'; import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell'; @@ -23,7 +23,6 @@ import Column from 'Components/Table/Column'; import TagListConnector from 'Components/TagListConnector'; import { icons } from 'Helpers/Props'; import { executeCommand } from 'Store/Actions/commandActions'; -import getProgressBarKind from 'Utilities/Artist/getProgressBarKind'; import formatBytes from 'Utilities/Number/formatBytes'; import translate from 'Utilities/String/translate'; import AlbumsCell from './AlbumsCell'; @@ -301,23 +300,18 @@ function ArtistIndexRow(props: ArtistIndexRowProps) { } if (name === 'trackProgress') { - const progress = trackCount - ? (trackFileCount / trackCount) * 100 - : 100; - return ( - ); diff --git a/frontend/src/Artist/Index/createArtistQueueDetailsSelector.ts b/frontend/src/Artist/Index/createArtistQueueDetailsSelector.ts new file mode 100644 index 000000000..d831fa797 --- /dev/null +++ b/frontend/src/Artist/Index/createArtistQueueDetailsSelector.ts @@ -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; diff --git a/frontend/src/Utilities/Artist/getProgressBarKind.js b/frontend/src/Utilities/Artist/getProgressBarKind.ts similarity index 58% rename from frontend/src/Utilities/Artist/getProgressBarKind.js rename to frontend/src/Utilities/Artist/getProgressBarKind.ts index eb3b2dd6e..f45387024 100644 --- a/frontend/src/Utilities/Artist/getProgressBarKind.js +++ b/frontend/src/Utilities/Artist/getProgressBarKind.ts @@ -1,6 +1,15 @@ 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) { return status === 'ended' ? kinds.SUCCESS : kinds.PRIMARY; } diff --git a/frontend/src/typings/Queue.ts b/frontend/src/typings/Queue.ts index b758ef432..2a3c239a4 100644 --- a/frontend/src/typings/Queue.ts +++ b/frontend/src/typings/Queue.ts @@ -25,6 +25,8 @@ interface Queue extends ModelBase { protocol: string; downloadClient: string; outputPath: string; + trackFileCount: number; + trackHasFileCount: number; artistId?: number; albumId?: number; } diff --git a/src/Lidarr.Api.V1/Queue/QueueResource.cs b/src/Lidarr.Api.V1/Queue/QueueResource.cs index 25fab081d..e8458909f 100644 --- a/src/Lidarr.Api.V1/Queue/QueueResource.cs +++ b/src/Lidarr.Api.V1/Queue/QueueResource.cs @@ -38,6 +38,8 @@ namespace Lidarr.Api.V1.Queue public bool DownloadClientHasPostImportCategory { get; set; } public string Indexer { get; set; } public string OutputPath { get; set; } + public int TrackFileCount { get; set; } + public int TrackHasFileCount { get; set; } public bool DownloadForced { get; set; } } @@ -53,6 +55,8 @@ namespace Lidarr.Api.V1.Queue var customFormats = model.RemoteAlbum?.CustomFormats; var customFormatScore = model.Artist?.QualityProfile?.Value?.CalculateCustomFormatScore(customFormats) ?? 0; + var albumRelease = model.Album?.AlbumReleases?.Value?.SingleOrDefault(x => x.Monitored); + return new QueueResource { Id = model.Id, @@ -80,6 +84,8 @@ namespace Lidarr.Api.V1.Queue DownloadClientHasPostImportCategory = model.DownloadClientHasPostImportCategory, Indexer = model.Indexer, OutputPath = model.OutputPath, + TrackFileCount = albumRelease?.Tracks?.Value?.Count ?? 0, + TrackHasFileCount = albumRelease?.Tracks?.Value?.Count(x => x.HasFile) ?? 0, DownloadForced = model.DownloadForced }; } diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 8b2eecae4..11c55a96b 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -111,12 +111,13 @@ "ArtistClickToChangeAlbum": "Click to change album", "ArtistEditor": "Artist Editor", "ArtistFolderFormat": "Artist Folder Format", + "ArtistIndexFooterDownloading": "Downloading", "ArtistIsMonitored": "Artist is monitored", "ArtistIsUnmonitored": "Artist is unmonitored", "ArtistMonitoring": "Artist Monitoring", "ArtistName": "Artist Name", "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", "Artists": "Artists", "ArtistsEditRootFolderHelpText": "Moving artists to the same root folder can be used to rename artist folders to match updated name or naming format",