parent
6d88a98282
commit
ac806a2933
|
@ -159,13 +159,15 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SeriesIndexProgressBar
|
<SeriesIndexProgressBar
|
||||||
|
seriesId={seriesId}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
status={status}
|
status={status}
|
||||||
episodeCount={episodeCount}
|
episodeCount={episodeCount}
|
||||||
episodeFileCount={episodeFileCount}
|
episodeFileCount={episodeFileCount}
|
||||||
totalEpisodeCount={totalEpisodeCount}
|
totalEpisodeCount={totalEpisodeCount}
|
||||||
posterWidth={posterWidth}
|
width={posterWidth}
|
||||||
detailedProgressBar={overviewOptions.detailedProgressBar}
|
detailedProgressBar={overviewOptions.detailedProgressBar}
|
||||||
|
isStandalone={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -173,13 +173,15 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SeriesIndexProgressBar
|
<SeriesIndexProgressBar
|
||||||
|
seriesId={seriesId}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
status={status}
|
status={status}
|
||||||
episodeCount={episodeCount}
|
episodeCount={episodeCount}
|
||||||
episodeFileCount={episodeFileCount}
|
episodeFileCount={episodeFileCount}
|
||||||
totalEpisodeCount={totalEpisodeCount}
|
totalEpisodeCount={totalEpisodeCount}
|
||||||
posterWidth={posterWidth}
|
width={posterWidth}
|
||||||
detailedProgressBar={detailedProgressBar}
|
detailedProgressBar={detailedProgressBar}
|
||||||
|
isStandalone={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showTitle ? <div className={styles.title}>{title}</div> : null}
|
{showTitle ? <div className={styles.title}>{title}</div> : null}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background-color: #5b5b5b;
|
background-color: #5b5b5b;
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
transition: width 200ms ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progressBar {
|
.progressBar {
|
||||||
|
|
|
@ -1,44 +1,66 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import ProgressBar from 'Components/ProgressBar';
|
import ProgressBar from 'Components/ProgressBar';
|
||||||
import { sizes } from 'Helpers/Props';
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import createSeriesQueueItemsDetailsSelector, {
|
||||||
|
SeriesQueueDetails,
|
||||||
|
} from 'Series/Index/createSeriesQueueDetailsSelector';
|
||||||
import getProgressBarKind from 'Utilities/Series/getProgressBarKind';
|
import getProgressBarKind from 'Utilities/Series/getProgressBarKind';
|
||||||
import styles from './SeriesIndexProgressBar.css';
|
import styles from './SeriesIndexProgressBar.css';
|
||||||
|
|
||||||
interface SeriesIndexProgressBarProps {
|
interface SeriesIndexProgressBarProps {
|
||||||
|
seriesId: number;
|
||||||
|
seasonNumber?: number;
|
||||||
monitored: boolean;
|
monitored: boolean;
|
||||||
status: string;
|
status: string;
|
||||||
episodeCount: number;
|
episodeCount: number;
|
||||||
episodeFileCount: number;
|
episodeFileCount: number;
|
||||||
totalEpisodeCount: number;
|
totalEpisodeCount: number;
|
||||||
posterWidth: number;
|
width: number;
|
||||||
detailedProgressBar: boolean;
|
detailedProgressBar: boolean;
|
||||||
|
isStandalone: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeriesIndexProgressBar(props: SeriesIndexProgressBarProps) {
|
function SeriesIndexProgressBar(props: SeriesIndexProgressBarProps) {
|
||||||
const {
|
const {
|
||||||
|
seriesId,
|
||||||
|
seasonNumber,
|
||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
episodeCount,
|
episodeCount,
|
||||||
episodeFileCount,
|
episodeFileCount,
|
||||||
totalEpisodeCount,
|
totalEpisodeCount,
|
||||||
posterWidth,
|
width,
|
||||||
detailedProgressBar,
|
detailedProgressBar,
|
||||||
|
isStandalone,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const queueDetails: SeriesQueueDetails = useSelector(
|
||||||
|
createSeriesQueueItemsDetailsSelector(seriesId, seasonNumber)
|
||||||
|
);
|
||||||
|
|
||||||
|
const newDownloads = queueDetails.count - queueDetails.episodesWithFiles;
|
||||||
const progress = episodeCount ? (episodeFileCount / episodeCount) * 100 : 100;
|
const progress = episodeCount ? (episodeFileCount / episodeCount) * 100 : 100;
|
||||||
const text = `${episodeFileCount} / ${episodeCount}`;
|
const text = newDownloads
|
||||||
|
? `${episodeFileCount} + ${newDownloads} / ${episodeCount}`
|
||||||
|
: `${episodeFileCount} / ${episodeCount}`;
|
||||||
|
|
||||||
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}
|
||||||
title={`${episodeFileCount} / ${episodeCount} (Total: ${totalEpisodeCount})`}
|
title={`${episodeFileCount} / ${episodeCount} (Total: ${totalEpisodeCount}, Downloading: ${queueDetails.count})`}
|
||||||
width={posterWidth}
|
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 { useDispatch, useSelector } from 'react-redux';
|
||||||
import { SelectProvider } from 'App/SelectContext';
|
import { SelectProvider } from 'App/SelectContext';
|
||||||
import { REFRESH_SERIES, RSS_SYNC } from 'Commands/commandNames';
|
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 SortDirection from 'Helpers/Props/SortDirection';
|
||||||
import NoSeries from 'Series/NoSeries';
|
import NoSeries from 'Series/NoSeries';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
import { fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||||
|
import { fetchSeries } from 'Store/Actions/seriesActions';
|
||||||
import {
|
import {
|
||||||
setSeriesFilter,
|
setSeriesFilter,
|
||||||
setSeriesSort,
|
setSeriesSort,
|
||||||
|
@ -88,6 +96,11 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
|
||||||
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(fetchSeries());
|
||||||
|
dispatch(fetchQueueDetails({ all: true }));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
const onRefreshSeriesPress = useCallback(() => {
|
const onRefreshSeriesPress = useCallback(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
executeCommand({
|
executeCommand({
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -114,6 +114,16 @@ export default function SeriesIndexFooter() {
|
||||||
/>
|
/>
|
||||||
<div>Missing Episodes (Series not monitored)</div>
|
<div>Missing Episodes (Series not monitored)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.legendItem}>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
styles.downloading,
|
||||||
|
enableColorImpairedMode && 'colorImpaired'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div>Downloading (One or more episodes)</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.statistics}>
|
<div className={styles.statistics}>
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import getProgressBarKind from 'Utilities/Series/getProgressBarKind';
|
import getProgressBarKind from 'Utilities/Series/getProgressBarKind';
|
||||||
import titleCase from 'Utilities/String/titleCase';
|
import titleCase from 'Utilities/String/titleCase';
|
||||||
|
import SeriesIndexProgressBar from '../ProgressBar/SeriesIndexProgressBar';
|
||||||
import hasGrowableColumns from './hasGrowableColumns';
|
import hasGrowableColumns from './hasGrowableColumns';
|
||||||
import SeasonsCell from './SeasonsCell';
|
import SeasonsCell from './SeasonsCell';
|
||||||
import selectTableOptions from './selectTableOptions';
|
import selectTableOptions from './selectTableOptions';
|
||||||
|
@ -306,19 +307,18 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'episodeProgress') {
|
if (name === 'episodeProgress') {
|
||||||
const progress = episodeCount
|
|
||||||
? (episodeFileCount / episodeCount) * 100
|
|
||||||
: 100;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||||
<ProgressBar
|
<SeriesIndexProgressBar
|
||||||
progress={progress}
|
seriesId={seriesId}
|
||||||
kind={getProgressBarKind(status, monitored, progress)}
|
monitored={monitored}
|
||||||
showText={true}
|
status={status}
|
||||||
text={`${episodeFileCount} / ${episodeCount}`}
|
episodeCount={episodeCount}
|
||||||
title={`${episodeFileCount} / ${episodeCount} (Total: ${totalEpisodeCount})`}
|
episodeFileCount={episodeFileCount}
|
||||||
|
totalEpisodeCount={totalEpisodeCount}
|
||||||
width={125}
|
width={125}
|
||||||
|
detailedProgressBar={true}
|
||||||
|
isStandalone={true}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
|
@ -330,21 +330,20 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const seasonStatistics = latestSeason.statistics || {};
|
const seasonStatistics = latestSeason.statistics || {};
|
||||||
const progress = seasonStatistics.episodeCount
|
|
||||||
? (seasonStatistics.episodeFileCount /
|
|
||||||
seasonStatistics.episodeCount) *
|
|
||||||
100
|
|
||||||
: 100;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||||
<ProgressBar
|
<SeriesIndexProgressBar
|
||||||
progress={progress}
|
seriesId={seriesId}
|
||||||
kind={getProgressBarKind(status, monitored, progress)}
|
seasonNumber={latestSeason.seasonNumber}
|
||||||
showText={true}
|
monitored={monitored}
|
||||||
text={`${seasonStatistics.episodeFileCount} / ${seasonStatistics.episodeCount}`}
|
status={status}
|
||||||
title={`${seasonStatistics.episodeFileCount} / ${seasonStatistics.episodeCount} (Total: ${seasonStatistics.totalEpisodeCount})`}
|
episodeCount={seasonStatistics.episodeCount}
|
||||||
|
episodeFileCount={seasonStatistics.episodeFileCount}
|
||||||
|
totalEpisodeCount={seasonStatistics.totalEpisodeCount}
|
||||||
width={125}
|
width={125}
|
||||||
|
detailedProgressBar={true}
|
||||||
|
isStandalone={true}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</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';
|
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;
|
||||||
}
|
}
|
|
@ -17,6 +17,7 @@ namespace Sonarr.Api.V3.Queue
|
||||||
{
|
{
|
||||||
public int? SeriesId { get; set; }
|
public int? SeriesId { get; set; }
|
||||||
public int? EpisodeId { get; set; }
|
public int? EpisodeId { get; set; }
|
||||||
|
public int? SeasonNumber { get; set; }
|
||||||
public SeriesResource Series { get; set; }
|
public SeriesResource Series { get; set; }
|
||||||
public EpisodeResource Episode { get; set; }
|
public EpisodeResource Episode { get; set; }
|
||||||
public List<Language> Languages { get; set; }
|
public List<Language> Languages { get; set; }
|
||||||
|
@ -37,6 +38,7 @@ namespace Sonarr.Api.V3.Queue
|
||||||
public string DownloadClient { get; set; }
|
public string DownloadClient { get; set; }
|
||||||
public string Indexer { get; set; }
|
public string Indexer { get; set; }
|
||||||
public string OutputPath { get; set; }
|
public string OutputPath { get; set; }
|
||||||
|
public bool EpisodeHasFile { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class QueueResourceMapper
|
public static class QueueResourceMapper
|
||||||
|
@ -53,6 +55,7 @@ namespace Sonarr.Api.V3.Queue
|
||||||
Id = model.Id,
|
Id = model.Id,
|
||||||
SeriesId = model.Series?.Id,
|
SeriesId = model.Series?.Id,
|
||||||
EpisodeId = model.Episode?.Id,
|
EpisodeId = model.Episode?.Id,
|
||||||
|
SeasonNumber = model.Episode?.SeasonNumber,
|
||||||
Series = includeSeries && model.Series != null ? model.Series.ToResource() : null,
|
Series = includeSeries && model.Series != null ? model.Series.ToResource() : null,
|
||||||
Episode = includeEpisode && model.Episode != null ? model.Episode.ToResource() : null,
|
Episode = includeEpisode && model.Episode != null ? model.Episode.ToResource() : null,
|
||||||
Languages = model.Languages,
|
Languages = model.Languages,
|
||||||
|
@ -72,7 +75,8 @@ namespace Sonarr.Api.V3.Queue
|
||||||
Protocol = model.Protocol,
|
Protocol = model.Protocol,
|
||||||
DownloadClient = model.DownloadClient,
|
DownloadClient = model.DownloadClient,
|
||||||
Indexer = model.Indexer,
|
Indexer = model.Indexer,
|
||||||
OutputPath = model.OutputPath
|
OutputPath = model.OutputPath,
|
||||||
|
EpisodeHasFile = model.Episode?.HasFile ?? false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue