From ac806a2933bf2dc0c96d471ec143fca8e1f5282f Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 12 Mar 2023 23:51:30 -0700 Subject: [PATCH] New: Show downloading status for series progress bar Closes #5474 --- .../Index/Overview/SeriesIndexOverview.tsx | 4 +- .../Index/Posters/SeriesIndexPoster.tsx | 4 +- .../ProgressBar/SeriesIndexProgressBar.css | 1 - .../ProgressBar/SeriesIndexProgressBar.tsx | 36 ++++++++++++---- frontend/src/Series/Index/SeriesIndex.tsx | 15 ++++++- .../src/Series/Index/SeriesIndexFooter.css | 6 +++ .../Series/Index/SeriesIndexFooter.css.d.ts | 1 + .../src/Series/Index/SeriesIndexFooter.tsx | 10 +++++ .../src/Series/Index/Table/SeriesIndexRow.tsx | 41 +++++++++--------- .../Index/createSeriesQueueDetailsSelector.ts | 42 +++++++++++++++++++ ...ogressBarKind.js => getProgressBarKind.ts} | 11 ++++- src/Sonarr.Api.V3/Queue/QueueResource.cs | 6 ++- 12 files changed, 143 insertions(+), 34 deletions(-) create mode 100644 frontend/src/Series/Index/createSeriesQueueDetailsSelector.ts rename frontend/src/Utilities/Series/{getProgressBarKind.js => getProgressBarKind.ts} (58%) diff --git a/frontend/src/Series/Index/Overview/SeriesIndexOverview.tsx b/frontend/src/Series/Index/Overview/SeriesIndexOverview.tsx index 87cb694db..b29b74655 100644 --- a/frontend/src/Series/Index/Overview/SeriesIndexOverview.tsx +++ b/frontend/src/Series/Index/Overview/SeriesIndexOverview.tsx @@ -159,13 +159,15 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) { diff --git a/frontend/src/Series/Index/Posters/SeriesIndexPoster.tsx b/frontend/src/Series/Index/Posters/SeriesIndexPoster.tsx index dd5ebe200..43857c2fc 100644 --- a/frontend/src/Series/Index/Posters/SeriesIndexPoster.tsx +++ b/frontend/src/Series/Index/Posters/SeriesIndexPoster.tsx @@ -173,13 +173,15 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) { {showTitle ?
{title}
: null} diff --git a/frontend/src/Series/Index/ProgressBar/SeriesIndexProgressBar.css b/frontend/src/Series/Index/ProgressBar/SeriesIndexProgressBar.css index ce5313877..9b5777117 100644 --- a/frontend/src/Series/Index/ProgressBar/SeriesIndexProgressBar.css +++ b/frontend/src/Series/Index/ProgressBar/SeriesIndexProgressBar.css @@ -4,7 +4,6 @@ border-radius: 0; background-color: #5b5b5b; color: var(--white); - transition: width 200ms ease; } .progressBar { diff --git a/frontend/src/Series/Index/ProgressBar/SeriesIndexProgressBar.tsx b/frontend/src/Series/Index/ProgressBar/SeriesIndexProgressBar.tsx index 66327a967..7faa55cf9 100644 --- a/frontend/src/Series/Index/ProgressBar/SeriesIndexProgressBar.tsx +++ b/frontend/src/Series/Index/ProgressBar/SeriesIndexProgressBar.tsx @@ -1,44 +1,66 @@ import React from 'react'; +import { useSelector } from 'react-redux'; import ProgressBar from 'Components/ProgressBar'; import { sizes } from 'Helpers/Props'; +import createSeriesQueueItemsDetailsSelector, { + SeriesQueueDetails, +} from 'Series/Index/createSeriesQueueDetailsSelector'; import getProgressBarKind from 'Utilities/Series/getProgressBarKind'; import styles from './SeriesIndexProgressBar.css'; interface SeriesIndexProgressBarProps { + seriesId: number; + seasonNumber?: number; monitored: boolean; status: string; episodeCount: number; episodeFileCount: number; totalEpisodeCount: number; - posterWidth: number; + width: number; detailedProgressBar: boolean; + isStandalone: boolean; } function SeriesIndexProgressBar(props: SeriesIndexProgressBarProps) { const { + seriesId, + seasonNumber, monitored, status, episodeCount, episodeFileCount, totalEpisodeCount, - posterWidth, + width, detailedProgressBar, + isStandalone, } = props; + const queueDetails: SeriesQueueDetails = useSelector( + createSeriesQueueItemsDetailsSelector(seriesId, seasonNumber) + ); + + const newDownloads = queueDetails.count - queueDetails.episodesWithFiles; const progress = episodeCount ? (episodeFileCount / episodeCount) * 100 : 100; - const text = `${episodeFileCount} / ${episodeCount}`; + const text = newDownloads + ? `${episodeFileCount} + ${newDownloads} / ${episodeCount}` + : `${episodeFileCount} / ${episodeCount}`; return ( 0 + )} size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL} showText={detailedProgressBar} text={text} - title={`${episodeFileCount} / ${episodeCount} (Total: ${totalEpisodeCount})`} - width={posterWidth} + title={`${episodeFileCount} / ${episodeCount} (Total: ${totalEpisodeCount}, Downloading: ${queueDetails.count})`} + width={width} /> ); } diff --git a/frontend/src/Series/Index/SeriesIndex.tsx b/frontend/src/Series/Index/SeriesIndex.tsx index 0c16b1692..93657e140 100644 --- a/frontend/src/Series/Index/SeriesIndex.tsx +++ b/frontend/src/Series/Index/SeriesIndex.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 { REFRESH_SERIES, RSS_SYNC } from 'Commands/commandNames'; @@ -16,6 +22,8 @@ import { align, icons } from 'Helpers/Props'; import SortDirection from 'Helpers/Props/SortDirection'; import NoSeries from 'Series/NoSeries'; import { executeCommand } from 'Store/Actions/commandActions'; +import { fetchQueueDetails } from 'Store/Actions/queueActions'; +import { fetchSeries } from 'Store/Actions/seriesActions'; import { setSeriesFilter, setSeriesSort, @@ -88,6 +96,11 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => { const [jumpToCharacter, setJumpToCharacter] = useState(null); const [isSelectMode, setIsSelectMode] = useState(false); + useEffect(() => { + dispatch(fetchSeries()); + dispatch(fetchQueueDetails({ all: true })); + }, [dispatch]); + const onRefreshSeriesPress = useCallback(() => { dispatch( executeCommand({ diff --git a/frontend/src/Series/Index/SeriesIndexFooter.css b/frontend/src/Series/Index/SeriesIndexFooter.css index c1c4b5a46..bf3fedfd6 100644 --- a/frontend/src/Series/Index/SeriesIndexFooter.css +++ b/frontend/src/Series/Index/SeriesIndexFooter.css @@ -50,6 +50,12 @@ } } +.downloading { + composes: legendItemColor; + + background-color: var(--purple); +} + .statistics { display: flex; justify-content: space-between; diff --git a/frontend/src/Series/Index/SeriesIndexFooter.css.d.ts b/frontend/src/Series/Index/SeriesIndexFooter.css.d.ts index b88d23a6c..29f693a8c 100644 --- a/frontend/src/Series/Index/SeriesIndexFooter.css.d.ts +++ b/frontend/src/Series/Index/SeriesIndexFooter.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/Series/Index/SeriesIndexFooter.tsx b/frontend/src/Series/Index/SeriesIndexFooter.tsx index b248a47d9..4981964cf 100644 --- a/frontend/src/Series/Index/SeriesIndexFooter.tsx +++ b/frontend/src/Series/Index/SeriesIndexFooter.tsx @@ -114,6 +114,16 @@ export default function SeriesIndexFooter() { />
Missing Episodes (Series not monitored)
+ +
+
+
Downloading (One or more episodes)
+
diff --git a/frontend/src/Series/Index/Table/SeriesIndexRow.tsx b/frontend/src/Series/Index/Table/SeriesIndexRow.tsx index fae5ba364..380ee46b4 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexRow.tsx +++ b/frontend/src/Series/Index/Table/SeriesIndexRow.tsx @@ -24,6 +24,7 @@ import { executeCommand } from 'Store/Actions/commandActions'; import formatBytes from 'Utilities/Number/formatBytes'; import getProgressBarKind from 'Utilities/Series/getProgressBarKind'; import titleCase from 'Utilities/String/titleCase'; +import SeriesIndexProgressBar from '../ProgressBar/SeriesIndexProgressBar'; import hasGrowableColumns from './hasGrowableColumns'; import SeasonsCell from './SeasonsCell'; import selectTableOptions from './selectTableOptions'; @@ -306,19 +307,18 @@ function SeriesIndexRow(props: SeriesIndexRowProps) { } if (name === 'episodeProgress') { - const progress = episodeCount - ? (episodeFileCount / episodeCount) * 100 - : 100; - return ( - ); @@ -330,21 +330,20 @@ function SeriesIndexRow(props: SeriesIndexRowProps) { } const seasonStatistics = latestSeason.statistics || {}; - const progress = seasonStatistics.episodeCount - ? (seasonStatistics.episodeFileCount / - seasonStatistics.episodeCount) * - 100 - : 100; return ( - ); diff --git a/frontend/src/Series/Index/createSeriesQueueDetailsSelector.ts b/frontend/src/Series/Index/createSeriesQueueDetailsSelector.ts new file mode 100644 index 000000000..23905e7b2 --- /dev/null +++ b/frontend/src/Series/Index/createSeriesQueueDetailsSelector.ts @@ -0,0 +1,42 @@ +import { createSelector } from 'reselect'; + +export interface SeriesQueueDetails { + count: number; + episodesWithFiles: number; +} + +function createSeriesQueueDetailsSelector( + seriesId: number, + seasonNumber?: number +) { + return createSelector( + (state) => state.queue.details.items, + (queueItems) => { + return queueItems.reduce( + (acc: SeriesQueueDetails, item) => { + if (item.seriesId !== seriesId) { + return acc; + } + + if (seasonNumber != null && item.seasonNumber !== seasonNumber) { + return acc; + } + + acc.count++; + + if (item.episodeHasFile) { + acc.episodesWithFiles++; + } + + return acc; + }, + { + count: 0, + episodesWithFiles: 0, + } + ); + } + ); +} + +export default createSeriesQueueDetailsSelector; diff --git a/frontend/src/Utilities/Series/getProgressBarKind.js b/frontend/src/Utilities/Series/getProgressBarKind.ts similarity index 58% rename from frontend/src/Utilities/Series/getProgressBarKind.js rename to frontend/src/Utilities/Series/getProgressBarKind.ts index eb3b2dd6e..f45387024 100644 --- a/frontend/src/Utilities/Series/getProgressBarKind.js +++ b/frontend/src/Utilities/Series/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/src/Sonarr.Api.V3/Queue/QueueResource.cs b/src/Sonarr.Api.V3/Queue/QueueResource.cs index 2d426f091..dd286960f 100644 --- a/src/Sonarr.Api.V3/Queue/QueueResource.cs +++ b/src/Sonarr.Api.V3/Queue/QueueResource.cs @@ -17,6 +17,7 @@ namespace Sonarr.Api.V3.Queue { public int? SeriesId { get; set; } public int? EpisodeId { get; set; } + public int? SeasonNumber { get; set; } public SeriesResource Series { get; set; } public EpisodeResource Episode { get; set; } public List Languages { get; set; } @@ -37,6 +38,7 @@ namespace Sonarr.Api.V3.Queue public string DownloadClient { get; set; } public string Indexer { get; set; } public string OutputPath { get; set; } + public bool EpisodeHasFile { get; set; } } public static class QueueResourceMapper @@ -53,6 +55,7 @@ namespace Sonarr.Api.V3.Queue Id = model.Id, SeriesId = model.Series?.Id, EpisodeId = model.Episode?.Id, + SeasonNumber = model.Episode?.SeasonNumber, Series = includeSeries && model.Series != null ? model.Series.ToResource() : null, Episode = includeEpisode && model.Episode != null ? model.Episode.ToResource() : null, Languages = model.Languages, @@ -72,7 +75,8 @@ namespace Sonarr.Api.V3.Queue Protocol = model.Protocol, DownloadClient = model.DownloadClient, Indexer = model.Indexer, - OutputPath = model.OutputPath + OutputPath = model.OutputPath, + EpisodeHasFile = model.Episode?.HasFile ?? false }; }