mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add Filter setting components
This commit is contained in:
parent
5598f49c32
commit
3ae27e05c7
14 changed files with 1392 additions and 8 deletions
457
src/apps/experimental/components/library/filter/FilterButton.tsx
Normal file
457
src/apps/experimental/components/library/filter/FilterButton.tsx
Normal file
|
@ -0,0 +1,457 @@
|
|||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp';
|
||||
import Box from '@mui/material/Box';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import Popover from '@mui/material/Popover';
|
||||
import MuiAccordion, { AccordionProps } from '@mui/material/Accordion';
|
||||
import MuiAccordionDetails from '@mui/material/AccordionDetails';
|
||||
import MuiAccordionSummary, {
|
||||
AccordionSummaryProps
|
||||
} from '@mui/material/AccordionSummary';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { useGetQueryFiltersLegacy, useGetStudios } from 'hooks/useFetchItems';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
import FiltersFeatures from './FiltersFeatures';
|
||||
import FiltersGenres from './FiltersGenres';
|
||||
import FiltersOfficialRatings from './FiltersOfficialRatings';
|
||||
import FiltersEpisodesStatus from './FiltersEpisodesStatus';
|
||||
import FiltersSeriesStatus from './FiltersSeriesStatus';
|
||||
import FiltersStatus from './FiltersStatus';
|
||||
import FiltersStudios from './FiltersStudios';
|
||||
import FiltersTags from './FiltersTags';
|
||||
import FiltersVideoTypes from './FiltersVideoTypes';
|
||||
import FiltersYears from './FiltersYears';
|
||||
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
import { LibraryTab } from 'types/libraryTab';
|
||||
|
||||
const Accordion = styled((props: AccordionProps) => (
|
||||
<MuiAccordion
|
||||
disableGutters
|
||||
elevation={0}
|
||||
TransitionProps={{ unmountOnExit: true }}
|
||||
square
|
||||
{...props}
|
||||
/>
|
||||
))(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0
|
||||
},
|
||||
'&:before': {
|
||||
display: 'none'
|
||||
}
|
||||
}));
|
||||
|
||||
const AccordionSummary = styled((props: AccordionSummaryProps) => (
|
||||
<MuiAccordionSummary
|
||||
expandIcon={<ArrowForwardIosSharpIcon sx={{ fontSize: '0.9rem' }} />}
|
||||
{...props}
|
||||
/>
|
||||
))(({ theme }) => ({
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark' ?
|
||||
'rgba(255, 255, 255, .05)' :
|
||||
'rgba(0, 0, 0, .03)',
|
||||
'& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': {
|
||||
transform: 'rotate(90deg)'
|
||||
},
|
||||
'& .MuiAccordionSummary-content': {
|
||||
marginLeft: theme.spacing(1)
|
||||
}
|
||||
}));
|
||||
|
||||
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
borderTop: '1px solid rgba(0, 0, 0, .125)'
|
||||
}));
|
||||
|
||||
interface FilterButtonProps {
|
||||
parentId: string | null | undefined;
|
||||
itemType: BaseItemKind;
|
||||
viewType: LibraryTab;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<
|
||||
React.SetStateAction<LibraryViewSettings>
|
||||
>;
|
||||
}
|
||||
|
||||
const FilterButton: FC<FilterButtonProps> = ({
|
||||
parentId,
|
||||
itemType,
|
||||
viewType,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const [expanded, setExpanded] = React.useState<string | false>(false);
|
||||
const open = Boolean(anchorEl);
|
||||
const id = open ? 'filter-popover' : undefined;
|
||||
|
||||
const { data } = useGetQueryFiltersLegacy(parentId, itemType);
|
||||
const { data: studios } = useGetStudios(parentId, itemType);
|
||||
|
||||
const handleChange =
|
||||
(panel: string) =>
|
||||
(event: React.SyntheticEvent, newExpanded: boolean) => {
|
||||
setExpanded(newExpanded ? panel : false);
|
||||
};
|
||||
|
||||
const handleClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setAnchorEl(null);
|
||||
}, []);
|
||||
|
||||
const isFiltersLegacyEnabled = () => {
|
||||
return (
|
||||
viewType === LibraryTab.Movies
|
||||
|| viewType === LibraryTab.Series
|
||||
|| viewType === LibraryTab.Albums
|
||||
|| viewType === LibraryTab.AlbumArtists
|
||||
|| viewType === LibraryTab.Artists
|
||||
|| viewType === LibraryTab.Songs
|
||||
|| viewType === LibraryTab.Episodes
|
||||
);
|
||||
};
|
||||
|
||||
const isFiltersStudiosEnabled = () => {
|
||||
return (
|
||||
viewType === LibraryTab.Movies
|
||||
|| viewType === LibraryTab.Series
|
||||
);
|
||||
};
|
||||
|
||||
const isFiltersFeaturesEnabled = () => {
|
||||
return (
|
||||
viewType === LibraryTab.Movies
|
||||
|| viewType === LibraryTab.Series
|
||||
|| viewType === LibraryTab.Episodes
|
||||
);
|
||||
};
|
||||
|
||||
const isFiltersVideoTypesEnabled = () => {
|
||||
return (
|
||||
viewType === LibraryTab.Movies
|
||||
|| viewType === LibraryTab.Episodes
|
||||
);
|
||||
};
|
||||
|
||||
const isFiltersSeriesStatusEnabled = () => {
|
||||
return viewType === LibraryTab.Series;
|
||||
};
|
||||
|
||||
const isFiltersEpisodesStatusEnabled = () => {
|
||||
return viewType === LibraryTab.Episodes;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<IconButton
|
||||
title={globalize.translate('Filter')}
|
||||
sx={{ ml: 2 }}
|
||||
aria-describedby={id}
|
||||
className='paper-icon-button-light btnShuffle autoSize'
|
||||
onClick={handleClick}
|
||||
>
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
maxHeight: '50%',
|
||||
width: 250
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersStatus'}
|
||||
onChange={handleChange('filtersStatus')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersStatus-content'
|
||||
id='filtersStatus-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Filters')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersStatus
|
||||
viewType={viewType}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
{isFiltersSeriesStatusEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersSeriesStatus'}
|
||||
onChange={handleChange('filtersSeriesStatus')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersSeriesStatus-content'
|
||||
id='filtersSeriesStatus-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('HeaderSeriesStatus')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersSeriesStatus
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
{isFiltersEpisodesStatusEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersEpisodesStatus'}
|
||||
onChange={handleChange('filtersEpisodesStatus')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersEpisodesStatus-content'
|
||||
id='filtersEpisodesStatus-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('HeaderEpisodesStatus')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersEpisodesStatus
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
{isFiltersFeaturesEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersFeatures'}
|
||||
onChange={handleChange('filtersFeatures')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersFeatures-content'
|
||||
id='filtersFeatures-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Features')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersFeatures
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isFiltersVideoTypesEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersVideoTypes'}
|
||||
onChange={handleChange('filtersVideoTypes')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersVideoTypes-content'
|
||||
id='filtersVideoTypes-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('HeaderVideoType')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersVideoTypes
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isFiltersLegacyEnabled() && (
|
||||
<>
|
||||
{data?.Genres && data?.Genres?.length > 0 && (
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersGenres'}
|
||||
onChange={handleChange('filtersGenres')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersGenres-content'
|
||||
id='filtersGenres-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Genres')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersGenres
|
||||
filtes={data}
|
||||
libraryViewSettings={
|
||||
libraryViewSettings
|
||||
}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{data?.OfficialRatings
|
||||
&& data?.OfficialRatings?.length > 0 && (
|
||||
<Accordion
|
||||
expanded={
|
||||
expanded === 'filtersOfficialRatings'
|
||||
}
|
||||
onChange={handleChange(
|
||||
'filtersOfficialRatings'
|
||||
)}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersOfficialRatings-content'
|
||||
id='filtersOfficialRatings-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate(
|
||||
'HeaderParentalRatings'
|
||||
)}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersOfficialRatings
|
||||
filtes={data}
|
||||
libraryViewSettings={
|
||||
libraryViewSettings
|
||||
}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{data?.Tags && data?.Tags.length > 0 && (
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersTags'}
|
||||
onChange={handleChange('filtersTags')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersTags-content'
|
||||
id='filtersTags-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Tags')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersTags
|
||||
filtes={data}
|
||||
libraryViewSettings={
|
||||
libraryViewSettings
|
||||
}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{data?.Years && data?.Years?.length > 0 && (
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersYears'}
|
||||
onChange={handleChange('filtersYears')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersYears-content'
|
||||
id='filtersYears-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('HeaderYears')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersYears
|
||||
filtes={data}
|
||||
libraryViewSettings={
|
||||
libraryViewSettings
|
||||
}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isFiltersStudiosEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersStudios'}
|
||||
onChange={handleChange('filtersStudios')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersStudios-content'
|
||||
id='filtersStudios-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Studios')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersStudios
|
||||
filtes={studios}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterButton;
|
|
@ -0,0 +1,75 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
const episodesStatusOptions = [
|
||||
{ label: 'OptionSpecialEpisode', value: 'ParentIndexNumber' },
|
||||
{ label: 'OptionMissingEpisode', value: 'IsMissing' },
|
||||
{ label: 'OptionUnairedEpisode', value: 'IsUnaired' }
|
||||
];
|
||||
|
||||
interface FiltersEpisodesStatusProps {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersEpisodesStatus: FC<FiltersEpisodesStatusProps> = ({
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersEpisodesStatusChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.EpisodesStatus;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, EpisodesStatus: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
EpisodesStatus: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.EpisodesStatus]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{episodesStatusOptions.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.EpisodesStatus?.includes(
|
||||
String( filter.value)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersEpisodesStatusChange}
|
||||
value={String(filter.value)}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate(filter.label)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersEpisodesStatus;
|
|
@ -0,0 +1,81 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
const featuresOptions = [
|
||||
{ label: 'Subtitles', value: 'HasSubtitles' },
|
||||
{ label: 'Trailers', value: 'HasTrailer' },
|
||||
{ label: 'Extras', value: 'HasSpecialFeature' },
|
||||
{ label: 'ThemeSongs', value: 'HasThemeSong' },
|
||||
{ label: 'ThemeVideos', value: 'HasThemeVideo' }
|
||||
];
|
||||
|
||||
interface FiltersFeaturesProps {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<
|
||||
React.SetStateAction<LibraryViewSettings>
|
||||
>;
|
||||
}
|
||||
|
||||
const FiltersFeatures: FC<FiltersFeaturesProps> = ({
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersFeaturesChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue =
|
||||
libraryViewSettings?.Filters?.Features;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Features: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Features: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Features]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{featuresOptions
|
||||
.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Features?.includes(
|
||||
String(filter.value)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersFeaturesChange}
|
||||
value={String(filter.value)}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate(filter.label)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersFeatures;
|
|
@ -0,0 +1,71 @@
|
|||
import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersGenresProps {
|
||||
filtes?: QueryFiltersLegacy;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersGenres: FC<FiltersGenresProps> = ({
|
||||
filtes,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersGenresChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.Genres;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Genres: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Genres: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Genres]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filtes?.Genres?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Genres?.includes(
|
||||
String(filter)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersGenresChange}
|
||||
value={String(filter)}
|
||||
/>
|
||||
}
|
||||
label={filter}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersGenres;
|
|
@ -0,0 +1,71 @@
|
|||
import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersOfficialRatingsProps {
|
||||
filtes?: QueryFiltersLegacy;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersOfficialRatings: FC<FiltersOfficialRatingsProps> = ({
|
||||
filtes,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersOfficialRatingsChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.OfficialRatings;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, OfficialRatings: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
OfficialRatings: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.OfficialRatings]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filtes?.OfficialRatings?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.OfficialRatings?.includes(
|
||||
String(filter)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersOfficialRatingsChange}
|
||||
value={String(filter)}
|
||||
/>
|
||||
}
|
||||
label={filter}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersOfficialRatings;
|
|
@ -0,0 +1,74 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
import { SeriesStatus } from '@jellyfin/sdk/lib/generated-client';
|
||||
|
||||
const statusFiltersOptions = [
|
||||
{ label: 'Continuing', value: SeriesStatus.Continuing },
|
||||
{ label: 'Ended', value: SeriesStatus.Ended },
|
||||
{ label: 'Unreleased', value: SeriesStatus.Unreleased }
|
||||
];
|
||||
|
||||
interface FiltersSeriesStatusProps {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersSeriesStatus: FC<FiltersSeriesStatusProps> = ({
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersSeriesStatusChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = event.target.value as SeriesStatus;
|
||||
const existingValue = libraryViewSettings?.Filters?.SeriesStatus;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: SeriesStatus) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, SeriesStatus: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
SeriesStatus: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.SeriesStatus]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{statusFiltersOptions.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.SeriesStatus?.includes( filter.value)
|
||||
}
|
||||
onChange={onFiltersSeriesStatusChange}
|
||||
value={filter.value}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate(filter.label)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersSeriesStatus;
|
|
@ -0,0 +1,97 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
import { ItemFilter } from '@jellyfin/sdk/lib/generated-client';
|
||||
import { LibraryTab } from 'types/libraryTab';
|
||||
|
||||
const statusFiltersOptions = [
|
||||
{ label: 'Played', value: ItemFilter.IsPlayed },
|
||||
{ label: 'Unplayed', value: ItemFilter.IsUnplayed },
|
||||
{ label: 'Favorite', value: ItemFilter.IsFavorite },
|
||||
{ label: 'ContinueWatching', value: ItemFilter.IsResumable }
|
||||
];
|
||||
|
||||
interface FiltersStatusProps {
|
||||
viewType: LibraryTab;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersStatus: FC<FiltersStatusProps> = ({
|
||||
viewType,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersStatusChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = event.target.value as ItemFilter;
|
||||
const existingValue = libraryViewSettings?.Filters?.Status;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: ItemFilter) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Status: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Status: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Status]
|
||||
);
|
||||
|
||||
const getVisibleFiltersStatus = () => {
|
||||
const visibleFiltersStatus: ItemFilter[] = [ItemFilter.IsFavorite];
|
||||
|
||||
if (
|
||||
viewType !== LibraryTab.Albums
|
||||
&& viewType !== LibraryTab.Artists
|
||||
&& viewType !== LibraryTab.AlbumArtists
|
||||
&& viewType !== LibraryTab.Songs
|
||||
) {
|
||||
visibleFiltersStatus.push(ItemFilter.IsUnplayed);
|
||||
visibleFiltersStatus.push(ItemFilter.IsPlayed);
|
||||
visibleFiltersStatus.push(ItemFilter.IsResumable);
|
||||
}
|
||||
|
||||
return visibleFiltersStatus;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{statusFiltersOptions
|
||||
.filter((filter) => getVisibleFiltersStatus().includes(filter.value))
|
||||
.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Status?.includes(filter.value)
|
||||
}
|
||||
onChange={onFiltersStatusChange}
|
||||
value={filter.value}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate(filter.label)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersStatus;
|
|
@ -0,0 +1,71 @@
|
|||
import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersStudiosProps {
|
||||
filtes?: BaseItemDtoQueryResult;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersStudios: FC<FiltersStudiosProps> = ({
|
||||
filtes,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersStudiosChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.StudioIds;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, StudioIds: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
StudioIds: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.StudioIds]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filtes?.Items?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.Id}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.StudioIds?.includes(
|
||||
String(filter.Id)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersStudiosChange}
|
||||
value={String(filter.Id)}
|
||||
/>
|
||||
}
|
||||
label={filter.Name}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersStudios;
|
|
@ -0,0 +1,71 @@
|
|||
import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersTagsProps {
|
||||
filtes?: QueryFiltersLegacy;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersTags: FC<FiltersTagsProps> = ({
|
||||
filtes,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersTagsChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.Tags;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Tags: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Tags: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Tags]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filtes?.Tags?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Tags?.includes(
|
||||
String(filter)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersTagsChange}
|
||||
value={String(filter)}
|
||||
/>
|
||||
}
|
||||
label={filter}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersTags;
|
|
@ -0,0 +1,131 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
import { VideoType } from '@jellyfin/sdk/lib/generated-client';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
const videoTypesOptions = [
|
||||
{ label: 'DVD', value: VideoType.Dvd },
|
||||
{ label: 'Blu-ray', value: VideoType.BluRay },
|
||||
{ label: 'ISO', value: VideoType.Iso }
|
||||
];
|
||||
|
||||
interface FiltersVideoTypesProps {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersVideoTypes: FC<FiltersVideoTypesProps> = ({
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersVideoTypesChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = event.target.value as VideoType;
|
||||
const existingValue = libraryViewSettings?.Filters?.VideoTypes;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: VideoType) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, VideoTypes: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
VideoTypes: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.VideoTypes]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const name = event.target.name;
|
||||
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
[name]: event.target.checked
|
||||
}));
|
||||
},
|
||||
[setLibraryViewSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.IsSD}
|
||||
onChange={handleChange}
|
||||
name='IsSD'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('SD')}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.IsHD}
|
||||
onChange={handleChange}
|
||||
name='IsHD'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('HD')}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
libraryViewSettings.Is4K
|
||||
}
|
||||
onChange={handleChange}
|
||||
name='Is4K'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('4K')}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
libraryViewSettings.Is3D
|
||||
}
|
||||
onChange={handleChange}
|
||||
name='Is3D'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('3D')}
|
||||
/>
|
||||
{videoTypesOptions
|
||||
.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.VideoTypes?.includes(filter.value)
|
||||
}
|
||||
onChange={onFiltersVideoTypesChange}
|
||||
value={filter.value}
|
||||
/>
|
||||
}
|
||||
label={filter.label}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersVideoTypes;
|
|
@ -0,0 +1,71 @@
|
|||
import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersYearsProps {
|
||||
filtes?: QueryFiltersLegacy;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersYears: FC<FiltersYearsProps> = ({
|
||||
filtes,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersYearsChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = Number(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.Years;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: number) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Years: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Years: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Years]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filtes?.Years?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Years?.includes(
|
||||
Number(filter)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersYearsChange}
|
||||
value={String(filter)}
|
||||
/>
|
||||
}
|
||||
label={filter}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersYears;
|
|
@ -7,9 +7,11 @@ import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-field
|
|||
import { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-filter';
|
||||
import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order';
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
|
||||
import { getFilterApi } from '@jellyfin/sdk/lib/utils/api/filter-api';
|
||||
import { getGenresApi } from '@jellyfin/sdk/lib/utils/api/genres-api';
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
||||
import { getMoviesApi } from '@jellyfin/sdk/lib/utils/api/movies-api';
|
||||
import { getStudiosApi } from '@jellyfin/sdk/lib/utils/api/studios-api';
|
||||
import { getTvShowsApi } from '@jellyfin/sdk/lib/utils/api/tv-shows-api';
|
||||
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
@ -19,7 +21,7 @@ import { Sections, SectionsViewType } from 'types/suggestionsSections';
|
|||
|
||||
const fetchGetItem = async (
|
||||
currentApi: JellyfinApiContext,
|
||||
parentId?: string | null,
|
||||
parentId: string | null | undefined,
|
||||
options?: AxiosRequestConfig
|
||||
) => {
|
||||
const { api, user } = currentApi;
|
||||
|
@ -37,7 +39,7 @@ const fetchGetItem = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const useGetItem = (parentId?: string | null) => {
|
||||
export const useGetItem = (parentId: string | null | undefined) => {
|
||||
const currentApi = useApi();
|
||||
return useQuery({
|
||||
queryKey: ['Item', parentId],
|
||||
|
@ -83,7 +85,7 @@ export const useGetItems = (parametersOptions: ItemsApiGetItemsRequest) => {
|
|||
|
||||
const fetchGetMovieRecommendations = async (
|
||||
currentApi: JellyfinApiContext,
|
||||
parentId?: string | null,
|
||||
parentId: string | null | undefined,
|
||||
options?: AxiosRequestConfig
|
||||
) => {
|
||||
const { api, user } = currentApi;
|
||||
|
@ -108,7 +110,7 @@ const fetchGetMovieRecommendations = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const useGetMovieRecommendations = (parentId?: string | null) => {
|
||||
export const useGetMovieRecommendations = (parentId: string | null | undefined) => {
|
||||
const currentApi = useApi();
|
||||
return useQuery({
|
||||
queryKey: ['MovieRecommendations', parentId],
|
||||
|
@ -121,7 +123,7 @@ export const useGetMovieRecommendations = (parentId?: string | null) => {
|
|||
const fetchGetItemsBySuggestionsType = async (
|
||||
currentApi: JellyfinApiContext,
|
||||
sections: Sections,
|
||||
parentId?: string | null,
|
||||
parentId: string | null | undefined,
|
||||
options?: AxiosRequestConfig
|
||||
) => {
|
||||
const { api, user } = currentApi;
|
||||
|
@ -234,7 +236,7 @@ const fetchGetItemsBySuggestionsType = async (
|
|||
|
||||
export const useGetItemsBySectionType = (
|
||||
sections: Sections,
|
||||
parentId?: string | null
|
||||
parentId: string | null | undefined
|
||||
) => {
|
||||
const currentApi = useApi();
|
||||
return useQuery({
|
||||
|
@ -253,7 +255,7 @@ export const useGetItemsBySectionType = (
|
|||
const fetchGetGenres = async (
|
||||
currentApi: JellyfinApiContext,
|
||||
itemType: BaseItemKind,
|
||||
parentId?: string | null,
|
||||
parentId: string | null | undefined,
|
||||
options?: AxiosRequestConfig
|
||||
) => {
|
||||
const { api, user } = currentApi;
|
||||
|
@ -275,7 +277,7 @@ const fetchGetGenres = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const useGetGenres = (itemType: BaseItemKind, parentId?: string | null) => {
|
||||
export const useGetGenres = (itemType: BaseItemKind, parentId: string | null | undefined) => {
|
||||
const currentApi = useApi();
|
||||
return useQuery({
|
||||
queryKey: ['Genres', parentId],
|
||||
|
@ -284,3 +286,78 @@ export const useGetGenres = (itemType: BaseItemKind, parentId?: string | null) =
|
|||
enabled: !!parentId
|
||||
});
|
||||
};
|
||||
|
||||
const fetchGetStudios = async (
|
||||
currentApi: JellyfinApiContext,
|
||||
parentId: string | null | undefined,
|
||||
itemType: BaseItemKind,
|
||||
options?: AxiosRequestConfig
|
||||
) => {
|
||||
const { api, user } = currentApi;
|
||||
if (api && user?.Id) {
|
||||
const response = await getStudiosApi(api).getStudios(
|
||||
{
|
||||
userId: user.Id,
|
||||
includeItemTypes: [itemType],
|
||||
fields: [
|
||||
ItemFields.DateCreated,
|
||||
ItemFields.PrimaryImageAspectRatio
|
||||
],
|
||||
enableImageTypes: [ImageType.Thumb],
|
||||
parentId: parentId ?? undefined,
|
||||
enableTotalRecordCount: false
|
||||
},
|
||||
{
|
||||
signal: options?.signal
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetStudios = (parentId: string | null | undefined, itemType: BaseItemKind) => {
|
||||
const currentApi = useApi();
|
||||
return useQuery({
|
||||
queryKey: ['Studios', parentId, itemType],
|
||||
queryFn: ({ signal }) =>
|
||||
fetchGetStudios(currentApi, parentId, itemType, { signal }),
|
||||
enabled: !!parentId
|
||||
});
|
||||
};
|
||||
|
||||
const fetchGetQueryFiltersLegacy = async (
|
||||
currentApi: JellyfinApiContext,
|
||||
parentId: string | null | undefined,
|
||||
itemType: BaseItemKind,
|
||||
options?: AxiosRequestConfig
|
||||
) => {
|
||||
const { api, user } = currentApi;
|
||||
if (api && user?.Id) {
|
||||
const response = await getFilterApi(api).getQueryFiltersLegacy(
|
||||
{
|
||||
userId: user.Id,
|
||||
parentId: parentId ?? undefined,
|
||||
includeItemTypes: [itemType]
|
||||
},
|
||||
{
|
||||
signal: options?.signal
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetQueryFiltersLegacy = (
|
||||
parentId: string | null | undefined,
|
||||
itemType: BaseItemKind
|
||||
) => {
|
||||
const currentApi = useApi();
|
||||
return useQuery({
|
||||
queryKey: ['QueryFiltersLegacy', parentId, itemType],
|
||||
queryFn: ({ signal }) =>
|
||||
fetchGetQueryFiltersLegacy(currentApi, parentId, itemType, {
|
||||
signal
|
||||
}),
|
||||
enabled: !!parentId
|
||||
});
|
||||
};
|
||||
|
|
|
@ -369,6 +369,7 @@
|
|||
"HeaderEditImages": "Edit Images",
|
||||
"HeaderEnabledFields": "Enabled Fields",
|
||||
"HeaderEnabledFieldsHelp": "Uncheck a field to lock it and prevent its data from being changed.",
|
||||
"HeaderEpisodesStatus": "Episodes Status",
|
||||
"HeaderError": "Error",
|
||||
"HeaderExternalIds": "External IDs",
|
||||
"HeaderFeatureAccess": "Feature access",
|
||||
|
|
|
@ -1,3 +1,39 @@
|
|||
import type { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-filter';
|
||||
import type { VideoType } from '@jellyfin/sdk/lib/generated-client/models/video-type';
|
||||
import type { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order';
|
||||
import type { SeriesStatus } from '@jellyfin/sdk/lib/generated-client/models/series-status';
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
|
||||
|
||||
export interface LibraryViewProps {
|
||||
parentId: string | null;
|
||||
}
|
||||
|
||||
interface Filters {
|
||||
Features?: string[];
|
||||
Genres?: string[];
|
||||
OfficialRatings?: string[];
|
||||
Status?: ItemFilter[];
|
||||
EpisodesStatus?: string[];
|
||||
SeriesStatus?: SeriesStatus[];
|
||||
StudioIds?: string[];
|
||||
Tags?: string[];
|
||||
VideoTypes?: VideoType[];
|
||||
Years?: number[];
|
||||
}
|
||||
|
||||
export interface LibraryViewSettings {
|
||||
SortBy: ItemSortBy;
|
||||
SortOrder: SortOrder;
|
||||
StartIndex: number;
|
||||
CardLayout: boolean;
|
||||
ImageType: string;
|
||||
ShowTitle: boolean;
|
||||
ShowYear?: boolean;
|
||||
Filters?: Filters;
|
||||
IsSD?: boolean;
|
||||
IsHD?: boolean;
|
||||
Is4K?: boolean;
|
||||
Is3D?: boolean;
|
||||
NameLessThan?: string | null;
|
||||
NameStartsWith?: string | null;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue