Merge pull request #8158 from TheMelmacian/feature/langugae_filters_for_modern_layout

This commit is contained in:
Bill Thornton
2026-07-20 17:39:02 -04:00
committed by GitHub
7 changed files with 229 additions and 13 deletions
@@ -15,9 +15,10 @@ import Badge from '@mui/material/Badge';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import { useGetQueryFiltersLegacy, useGetStudios } from 'hooks/useFetchItems';
import { useGetQueryFilters, useGetQueryFiltersLegacy, useGetStudios } from 'hooks/useFetchItems';
import globalize from 'lib/globalize';
import FiltersAudioLanguages from './FiltersAudioLanguages';
import FiltersFeatures from './FiltersFeatures';
import FiltersGenres from './FiltersGenres';
import FiltersOfficialRatings from './FiltersOfficialRatings';
@@ -25,6 +26,7 @@ import FiltersEpisodesStatus from './FiltersEpisodesStatus';
import FiltersSeriesStatus from './FiltersSeriesStatus';
import FiltersStatus from './FiltersStatus';
import FiltersStudios from './FiltersStudios';
import FiltersSubtitleLanguages from './FiltersSubtitleLanguages';
import FiltersTags from './FiltersTags';
import FiltersVideoTypes from './FiltersVideoTypes';
import FiltersYears from './FiltersYears';
@@ -99,7 +101,8 @@ const FilterButton: FC<FilterButtonProps> = ({
const open = Boolean(anchorEl);
const id = open ? 'filter-popover' : undefined;
const { data } = useGetQueryFiltersLegacy(parentId, itemType);
const { data: filtersLegacy } = useGetQueryFiltersLegacy(parentId, itemType);
const { data: filters } = useGetQueryFilters(parentId, itemType);
const { data: studios } = useGetStudios(parentId, itemType);
const handleChange =
@@ -173,6 +176,13 @@ const FilterButton: FC<FilterButtonProps> = ({
return viewType === LibraryTab.Episodes;
};
const isFiltersLanguagesEnabled = () => {
return viewType === LibraryTab.Movies
|| viewType === LibraryTab.Series
|| viewType === LibraryTab.Episodes
|| viewType === LibraryTab.Mixed;
};
return (
<>
<Button
@@ -324,7 +334,7 @@ const FilterButton: FC<FilterButtonProps> = ({
{isFiltersLegacyEnabled() && (
<>
{data?.Genres && data?.Genres?.length > 0 && (
{filtersLegacy?.Genres && filtersLegacy?.Genres?.length > 0 && (
<Accordion
expanded={expanded === 'filtersGenres'}
onChange={handleChange('filtersGenres')}
@@ -339,7 +349,7 @@ const FilterButton: FC<FilterButtonProps> = ({
</AccordionSummary>
<AccordionDetails>
<FiltersGenres
genresOptions={data.Genres}
genresOptions={filtersLegacy.Genres}
libraryViewSettings={
libraryViewSettings
}
@@ -351,8 +361,8 @@ const FilterButton: FC<FilterButtonProps> = ({
</Accordion>
)}
{data?.OfficialRatings
&& data?.OfficialRatings?.length > 0 && (
{filtersLegacy?.OfficialRatings
&& filtersLegacy?.OfficialRatings?.length > 0 && (
<Accordion
expanded={
expanded === 'filtersOfficialRatings'
@@ -373,7 +383,7 @@ const FilterButton: FC<FilterButtonProps> = ({
</AccordionSummary>
<AccordionDetails>
<FiltersOfficialRatings
OfficialRatingsOptions={data.OfficialRatings}
OfficialRatingsOptions={filtersLegacy.OfficialRatings}
libraryViewSettings={
libraryViewSettings
}
@@ -385,7 +395,7 @@ const FilterButton: FC<FilterButtonProps> = ({
</Accordion>
)}
{data?.Tags && data?.Tags.length > 0 && (
{filtersLegacy?.Tags && filtersLegacy?.Tags.length > 0 && (
<Accordion
expanded={expanded === 'filtersTags'}
onChange={handleChange('filtersTags')}
@@ -400,7 +410,7 @@ const FilterButton: FC<FilterButtonProps> = ({
</AccordionSummary>
<AccordionDetails>
<FiltersTags
tagsOptions={data.Tags}
tagsOptions={filtersLegacy.Tags}
libraryViewSettings={
libraryViewSettings
}
@@ -412,7 +422,7 @@ const FilterButton: FC<FilterButtonProps> = ({
</Accordion>
)}
{data?.Years && data?.Years?.length > 0 && (
{filtersLegacy?.Years && filtersLegacy?.Years?.length > 0 && (
<Accordion
expanded={expanded === 'filtersYears'}
onChange={handleChange('filtersYears')}
@@ -427,7 +437,7 @@ const FilterButton: FC<FilterButtonProps> = ({
</AccordionSummary>
<AccordionDetails>
<FiltersYears
yearsOptions={data.Years}
yearsOptions={filtersLegacy.Years}
libraryViewSettings={
libraryViewSettings
}
@@ -464,6 +474,50 @@ const FilterButton: FC<FilterButtonProps> = ({
</AccordionDetails>
</Accordion>
)}
{isFiltersLanguagesEnabled() && !!filters?.AudioLanguages?.length && (
<Accordion
expanded={expanded === 'filtersAudioLanguages'}
onChange={handleChange('filtersAudioLanguages')}
>
<AccordionSummary
aria-controls='audioLanguages-content'
id='filtersAudioLanguages-header'
>
<Typography>
{globalize.translate('AudioLanguages')}
</Typography>
</AccordionSummary>
<AccordionDetails>
<FiltersAudioLanguages
options={filters.AudioLanguages}
libraryViewSettings={libraryViewSettings}
setLibraryViewSettings={setLibraryViewSettings}
/>
</AccordionDetails>
</Accordion>
)}
{isFiltersLanguagesEnabled() && !!filters?.SubtitleLanguages?.length && (
<Accordion
expanded={expanded === 'filtersSubtitleLanguages'}
onChange={handleChange('filtersSubtitleLanguages')}
>
<AccordionSummary
aria-controls='subtitleLanguages-content'
id='filtersSubtitleLanguages-header'
>
<Typography>
{globalize.translate('SubtitleLanguages')}
</Typography>
</AccordionSummary>
<AccordionDetails>
<FiltersSubtitleLanguages
options={filters.SubtitleLanguages}
libraryViewSettings={libraryViewSettings}
setLibraryViewSettings={setLibraryViewSettings}
/>
</AccordionDetails>
</Accordion>
)}
<Button
disabled={!hasFilters}
title={globalize.translate('ResetFilters')}
@@ -0,0 +1,59 @@
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormGroup from '@mui/material/FormGroup';
import React, { FC, useCallback } from 'react';
import { LibraryViewSettings } from 'types/library';
interface FiltersAudioLanguagesProps {
options: { Name?: string | null, Value?: string | null }[];
libraryViewSettings: LibraryViewSettings;
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
}
const FiltersAudioLanguages: FC<FiltersAudioLanguagesProps> = ({
options,
libraryViewSettings,
setLibraryViewSettings
}) => {
const onFiltersChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = event.target.value;
const existingValues = libraryViewSettings?.Filters?.AudioLanguages ?? [];
const updatedValues = existingValues.includes(value) ?
existingValues.filter((filter) => filter !== value) :
[...existingValues, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
AudioLanguages: updatedValues.length ? updatedValues : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.AudioLanguages]
);
return (
<FormGroup>
{options.filter((filter) => filter?.Value != null).map((filter) => (
<FormControlLabel
key={filter.Value}
control={
<Checkbox
checked={!!libraryViewSettings?.Filters?.AudioLanguages?.includes(filter.Value!)}
onChange={onFiltersChange}
value={filter.Value}
/>
}
label={filter.Name}
/>
))}
</FormGroup>
);
};
export default FiltersAudioLanguages;
@@ -0,0 +1,59 @@
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormGroup from '@mui/material/FormGroup';
import React, { FC, useCallback } from 'react';
import { LibraryViewSettings } from 'types/library';
interface FiltersSubtitleLanguagesProps {
options: { Name?: string | null, Value?: string | null }[];
libraryViewSettings: LibraryViewSettings;
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
}
const FiltersSubtitleLanguages: FC<FiltersSubtitleLanguagesProps> = ({
options,
libraryViewSettings,
setLibraryViewSettings
}) => {
const onFiltersChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = event.target.value;
const existingValues = libraryViewSettings?.Filters?.SubtitleLanguages ?? [];
const updatedValues = existingValues.includes(value) ?
existingValues.filter((filter) => filter !== value) :
[...existingValues, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
SubtitleLanguages: updatedValues.length ? updatedValues : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.SubtitleLanguages]
);
return (
<FormGroup>
{options.filter((filter) => filter?.Value != null).map((filter) => (
<FormControlLabel
key={filter.Value}
control={
<Checkbox
checked={!!libraryViewSettings?.Filters?.SubtitleLanguages?.includes(filter.Value!)}
onChange={onFiltersChange}
value={filter.Value}
/>
}
label={filter.Name}
/>
))}
</FormGroup>
);
};
export default FiltersSubtitleLanguages;
+38
View File
@@ -169,6 +169,28 @@ const fetchGetQueryFiltersLegacy = async (
}
};
const fetchGetQueryFilters = async (
currentApi: JellyfinApiContext,
parentId: ParentId,
itemType: BaseItemKind[],
options?: AxiosRequestConfig
) => {
const { api, user } = currentApi;
if (api && user?.Id) {
const response = await getFilterApi(api).getQueryFilters(
{
userId: user.Id,
parentId: parentId ?? undefined,
includeItemTypes: itemType
},
{
signal: options?.signal
}
);
return response.data;
}
};
export const useGetQueryFiltersLegacy = (
parentId: ParentId,
itemType: BaseItemKind[]
@@ -185,6 +207,22 @@ export const useGetQueryFiltersLegacy = (
});
};
export const useGetQueryFilters = (
parentId: ParentId,
itemType: BaseItemKind[]
) => {
const currentApi = useApi();
const isLivetv = parentId === 'livetv';
return useQuery({
queryKey: ['QueryFilters', parentId, itemType],
queryFn: ({ signal }) =>
fetchGetQueryFilters(currentApi, parentId, itemType, {
signal
}),
enabled: !!currentApi.api && !!currentApi.user?.Id && !!parentId && !isLivetv
});
};
const fetchGetItemsViewByType = async (
currentApi: JellyfinApiContext,
viewType: LibraryTab | undefined,
+3 -1
View File
@@ -1931,5 +1931,7 @@
"LabelProgressAndSpeed": "Progress / speed",
"LabelReasons": "Reasons",
"LabelPlayerSizes": "Player sizes",
"LabelPlaybackQuality": "Frame dropped / corrupted"
"LabelPlaybackQuality": "Frame dropped / corrupted",
"AudioLanguages": "Audio languages",
"SubtitleLanguages": "Subtitle languages"
}
+2
View File
@@ -45,6 +45,8 @@ export interface Filters {
VideoBasicFilter?: VideoBasicFilter[];
VideoTypes?: VideoType[];
Years?: number[];
AudioLanguages?: string[];
SubtitleLanguages?: string[];
}
export enum ViewMode {
+3 -1
View File
@@ -150,7 +150,9 @@ export const getFiltersQuery = (
officialRatings: libraryViewSettings?.Filters?.OfficialRatings,
tags: libraryViewSettings?.Filters?.Tags,
years: libraryViewSettings?.Filters?.Years,
studioIds: libraryViewSettings?.Filters?.StudioIds
studioIds: libraryViewSettings?.Filters?.StudioIds,
audioLanguages: libraryViewSettings?.Filters?.AudioLanguages,
subtitleLanguages: libraryViewSettings?.Filters?.SubtitleLanguages
};
};