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…
Reference in New Issue