New: Average Size per Episode column on Series list

Closes #8513
This commit is contained in:
Mark McDowall 2026-04-15 16:46:51 -07:00
parent 10192460ac
commit c6f394ccd7
9 changed files with 70 additions and 0 deletions

View file

@ -154,6 +154,15 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
{translate('SizeOnDisk')}
</SortMenuItem>
<SortMenuItem
name="averageSizePerEpisode"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('AverageSizePerEpisode')}
</SortMenuItem>
<SortMenuItem
name="tags"
sortKey={sortKey}

View file

@ -131,6 +131,12 @@
flex: 0 0 120px;
}
.averageSizePerEpisode {
composes: cell;
flex: 0 0 160px;
}
.ratings {
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';

View file

@ -3,6 +3,7 @@
interface CssExports {
'actions': string;
'added': string;
'averageSizePerEpisode': string;
'banner': string;
'bannerGrow': string;
'bannerImage': string;

View file

@ -396,6 +396,17 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
);
}
if (name === 'averageSizePerEpisode') {
const averageSize =
totalEpisodeCount > 0 ? sizeOnDisk / totalEpisodeCount : 0;
return (
<VirtualTableRowCell key={name} className={styles[name]}>
{averageSize ? formatBytes(averageSize) : null}
</VirtualTableRowCell>
);
}
if (name === 'genres') {
const joinedGenres = genres.join(', ');

View file

@ -92,6 +92,12 @@
flex: 0 0 120px;
}
.averageSizePerEpisode {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 160px;
}
.ratings {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';

View file

@ -3,6 +3,7 @@
interface CssExports {
'actions': string;
'added': string;
'averageSizePerEpisode': string;
'banner': string;
'bannerGrow': string;
'certification': string;

View file

@ -191,6 +191,12 @@ const { useOptions, useOption, setOptions, setOption, setSort, getOptions } =
isSortable: true,
isVisible: false,
},
{
name: 'averageSizePerEpisode',
label: () => translate('AverageSize'),
isSortable: true,
isVisible: false,
},
{
name: 'genres',
label: () => translate('Genres'),

View file

@ -113,6 +113,14 @@ const SORT_PREDICATES = {
return item.statistics?.sizeOnDisk ?? 0;
},
averageSizePerEpisode: (item: Series, _direction: SortDirection) => {
const totalEpisodeCount = item.statistics?.totalEpisodeCount ?? 0;
return totalEpisodeCount > 0
? (item.statistics?.sizeOnDisk ?? 0) / totalEpisodeCount
: 0;
},
network: (item: Series, _direction: SortDirection) => {
const network = item.network;
@ -258,6 +266,20 @@ const FILTER_PREDICATES = {
return predicate(sizeOnDisk, filterValue);
},
averageSizePerEpisode: (
item: Series,
filterValue: number,
type: FilterType
) => {
const predicate = getFilterTypePredicate(type);
const totalEpisodeCount = item.statistics?.totalEpisodeCount ?? 0;
const averageSize =
totalEpisodeCount > 0
? (item.statistics?.sizeOnDisk ?? 0) / totalEpisodeCount
: 0;
return predicate(averageSize, filterValue);
},
hasMissingSeason: (item: Series, filterValue: boolean, type: FilterType) => {
const predicate = getFilterTypePredicate(type);
const seasons = item.seasons ?? [];
@ -444,6 +466,12 @@ export const FILTER_BUILDER: FilterBuilderProp<Series>[] = [
type: filterBuilderTypes.NUMBER,
valueType: filterBuilderValueTypes.BYTES,
},
{
name: 'averageSizePerEpisode',
label: () => translate('AverageSizePerEpisode'),
type: filterBuilderTypes.NUMBER,
valueType: filterBuilderValueTypes.BYTES,
},
{
name: 'genres',
label: () => translate('Genres'),

View file

@ -148,6 +148,8 @@
"AutomaticAdd": "Automatic Add",
"AutomaticSearch": "Automatic Search",
"AutomaticUpdatesDisabledDocker": "Automatic updates are not directly supported when using the Docker update mechanism. You will need to update the container image outside of {appName} or use a script",
"AverageSize": "Average Size",
"AverageSizePerEpisode": "Average Size per Episode",
"Backup": "Backup",
"BackupFolderHelpText": "Relative paths will be under {appName}'s AppData directory",
"BackupIntervalHelpText": "Interval between automatic backups",