mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2026-09-03 03:50:02 +03:00
Merge pull request #8158 from TheMelmacian/feature/langugae_filters_for_modern_layout
This commit is contained in:
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ export interface Filters {
|
||||
VideoBasicFilter?: VideoBasicFilter[];
|
||||
VideoTypes?: VideoType[];
|
||||
Years?: number[];
|
||||
AudioLanguages?: string[];
|
||||
SubtitleLanguages?: string[];
|
||||
}
|
||||
|
||||
export enum ViewMode {
|
||||
|
||||
+3
-1
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user