New: Show downloading status for series progress bar

Closes #5474
pull/5493/head
Mark McDowall 1 year ago
parent 6d88a98282
commit ac806a2933

@ -159,13 +159,15 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
</div>
<SeriesIndexProgressBar
seriesId={seriesId}
monitored={monitored}
status={status}
episodeCount={episodeCount}
episodeFileCount={episodeFileCount}
totalEpisodeCount={totalEpisodeCount}
posterWidth={posterWidth}
width={posterWidth}
detailedProgressBar={overviewOptions.detailedProgressBar}
isStandalone={false}
/>
</div>

@ -173,13 +173,15 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
</div>
<SeriesIndexProgressBar
seriesId={seriesId}
monitored={monitored}
status={status}
episodeCount={episodeCount}
episodeFileCount={episodeFileCount}
totalEpisodeCount={totalEpisodeCount}
posterWidth={posterWidth}
width={posterWidth}
detailedProgressBar={detailedProgressBar}
isStandalone={false}
/>
{showTitle ? <div className={styles.title}>{title}</div> : null}

@ -4,7 +4,6 @@
border-radius: 0;
background-color: #5b5b5b;
color: var(--white);
transition: width 200ms ease;
}
.progressBar {

@ -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 (
<ProgressBar
className={styles.progressBar}
containerClassName={styles.progress}
containerClassName={isStandalone ? undefined : styles.progress}
progress={progress}
kind={getProgressBarKind(status, monitored, progress)}
kind={getProgressBarKind(
status,
monitored,
progress,
queueDetails.count > 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}
/>
);
}

@ -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<string | null>(null);
const [isSelectMode, setIsSelectMode] = useState(false);
useEffect(() => {
dispatch(fetchSeries());
dispatch(fetchQueueDetails({ all: true }));
}, [dispatch]);
const onRefreshSeriesPress = useCallback(() => {
dispatch(
executeCommand({

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

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

@ -114,6 +114,16 @@ export default function SeriesIndexFooter() {
/>
<div>Missing Episodes (Series not monitored)</div>
</div>
<div className={styles.legendItem}>
<div
className={classNames(
styles.downloading,
enableColorImpairedMode && 'colorImpaired'
)}
/>
<div>Downloading (One or more episodes)</div>
</div>
</div>
<div className={styles.statistics}>

@ -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 (
<VirtualTableRowCell key={name} className={styles[name]}>
<ProgressBar
progress={progress}
kind={getProgressBarKind(status, monitored, progress)}
showText={true}
text={`${episodeFileCount} / ${episodeCount}`}
title={`${episodeFileCount} / ${episodeCount} (Total: ${totalEpisodeCount})`}
<SeriesIndexProgressBar
seriesId={seriesId}
monitored={monitored}
status={status}
episodeCount={episodeCount}
episodeFileCount={episodeFileCount}
totalEpisodeCount={totalEpisodeCount}
width={125}
detailedProgressBar={true}
isStandalone={true}
/>
</VirtualTableRowCell>
);
@ -330,21 +330,20 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
}
const seasonStatistics = latestSeason.statistics || {};
const progress = seasonStatistics.episodeCount
? (seasonStatistics.episodeFileCount /
seasonStatistics.episodeCount) *
100
: 100;
return (
<VirtualTableRowCell key={name} className={styles[name]}>
<ProgressBar
progress={progress}
kind={getProgressBarKind(status, monitored, progress)}
showText={true}
text={`${seasonStatistics.episodeFileCount} / ${seasonStatistics.episodeCount}`}
title={`${seasonStatistics.episodeFileCount} / ${seasonStatistics.episodeCount} (Total: ${seasonStatistics.totalEpisodeCount})`}
<SeriesIndexProgressBar
seriesId={seriesId}
seasonNumber={latestSeason.seasonNumber}
monitored={monitored}
status={status}
episodeCount={seasonStatistics.episodeCount}
episodeFileCount={seasonStatistics.episodeFileCount}
totalEpisodeCount={seasonStatistics.totalEpisodeCount}
width={125}
detailedProgressBar={true}
isStandalone={true}
/>
</VirtualTableRowCell>
);

@ -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;

@ -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;
}

@ -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<Language> 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
};
}

Loading…
Cancel
Save