1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #4776 from grafixeyehero/Add-filters-status-indicator

This commit is contained in:
Bill Thornton 2023-09-16 22:00:44 -04:00 committed by GitHub
commit 5fb4b51cfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 235 additions and 292 deletions

View file

@ -10,6 +10,7 @@ import MuiAccordionSummary, {
AccordionSummaryProps
} from '@mui/material/AccordionSummary';
import IconButton from '@mui/material/IconButton';
import { Badge } from '@mui/material';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
@ -152,6 +153,9 @@ const FilterButton: FC<FilterButtonProps> = ({
return viewType === LibraryTab.Episodes;
};
const hasFilters =
Object.values(libraryViewSettings.Filters || {}).some((filter) => !!filter);
return (
<Box>
<IconButton
@ -161,7 +165,9 @@ const FilterButton: FC<FilterButtonProps> = ({
className='paper-icon-button-light btnShuffle autoSize'
onClick={handleClick}
>
<FilterListIcon />
<Badge color='info' variant='dot' invisible={!hasFilters}>
<FilterListIcon />
</Badge>
</IconButton>
<Popover
id={id}
@ -239,7 +245,9 @@ const FilterButton: FC<FilterButtonProps> = ({
id='filtersEpisodesStatus-header'
>
<Typography>
{globalize.translate('HeaderEpisodesStatus')}
{globalize.translate(
'HeaderEpisodesStatus'
)}
</Typography>
</AccordionSummary>
<AccordionDetails>

View file

@ -3,12 +3,12 @@ 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 { EpisodeFilter, LibraryViewSettings } from 'types/library';
const episodesStatusOptions = [
{ label: 'OptionSpecialEpisode', value: 'ParentIndexNumber' },
{ label: 'OptionMissingEpisode', value: 'IsMissing' },
{ label: 'OptionUnairedEpisode', value: 'IsUnaired' }
const episodeFilterOptions = [
{ label: 'OptionSpecialEpisode', value: EpisodeFilter.ParentIndexNumber },
{ label: 'OptionMissingEpisode', value: EpisodeFilter.IsMissing },
{ label: 'OptionUnairedEpisode', value: EpisodeFilter.IsUnaired }
];
interface FiltersEpisodesStatusProps {
@ -23,46 +23,39 @@ const FiltersEpisodesStatus: FC<FiltersEpisodesStatusProps> = ({
const onFiltersEpisodesStatusChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = String(event.target.value);
const existingValue = libraryViewSettings?.Filters?.EpisodesStatus;
const value = event.target.value as EpisodeFilter;
const existingEpisodeFilter = libraryViewSettings?.Filters?.EpisodeFilter ?? [];
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]
}
}));
}
const updatedEpisodeFilter = existingEpisodeFilter.includes(value) ?
existingEpisodeFilter.filter((filter) => filter !== value) :
[...existingEpisodeFilter, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
EpisodeFilter: updatedEpisodeFilter.length ? updatedEpisodeFilter : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.EpisodesStatus]
[setLibraryViewSettings, libraryViewSettings?.Filters?.EpisodeFilter]
);
return (
<FormGroup>
{episodesStatusOptions.map((filter) => (
{episodeFilterOptions.map((filter) => (
<FormControlLabel
key={filter.value}
control={
<Checkbox
checked={
!!libraryViewSettings?.Filters?.EpisodesStatus?.includes(
String( filter.value)
!!libraryViewSettings?.Filters?.EpisodeFilter?.includes(
filter.value
)
}
onChange={onFiltersEpisodesStatusChange}
value={String(filter.value)}
value={filter.value}
/>
}
label={globalize.translate(filter.label)}

View file

@ -3,14 +3,14 @@ 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 { FeatureFilters, 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' }
{ label: 'Subtitles', value: FeatureFilters.HasSubtitles },
{ label: 'Trailers', value: FeatureFilters.HasTrailer },
{ label: 'Extras', value: FeatureFilters.HasSpecialFeature },
{ label: 'ThemeSongs', value: FeatureFilters.HasThemeSong },
{ label: 'ThemeVideos', value: FeatureFilters.HasThemeVideo }
];
interface FiltersFeaturesProps {
@ -27,29 +27,21 @@ const FiltersFeatures: FC<FiltersFeaturesProps> = ({
const onFiltersFeaturesChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = String(event.target.value);
const existingValue =
libraryViewSettings?.Filters?.Features;
const value = event.target.value as FeatureFilters;
const existingFeatures = 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]
}
}));
}
const updatedFeatures = existingFeatures.includes(value) ?
existingFeatures.filter((filter) => filter !== value) :
[...existingFeatures, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
Features: updatedFeatures.length ? updatedFeatures : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.Features]
);
@ -64,11 +56,11 @@ const FiltersFeatures: FC<FiltersFeaturesProps> = ({
<Checkbox
checked={
!!libraryViewSettings?.Filters?.Features?.includes(
String(filter.value)
filter.value
)
}
onChange={onFiltersFeaturesChange}
value={String(filter.value)}
value={filter.value}
/>
}
label={globalize.translate(filter.label)}

View file

@ -19,28 +19,21 @@ const FiltersGenres: FC<FiltersGenresProps> = ({
const onFiltersGenresChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = String(event.target.value);
const existingValue = libraryViewSettings?.Filters?.Genres;
const value = event.target.value;
const existingGenres = 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]
}
}));
}
const updatedGenres = existingGenres.includes(value) ?
existingGenres.filter((filter) => filter !== value) :
[...existingGenres, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
Genres: updatedGenres.length ? updatedGenres : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.Genres]
);

View file

@ -19,28 +19,21 @@ const FiltersOfficialRatings: FC<FiltersOfficialRatingsProps> = ({
const onFiltersOfficialRatingsChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = String(event.target.value);
const existingValue = libraryViewSettings?.Filters?.OfficialRatings;
const value = event.target.value;
const existingOfficialRatings = 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]
}
}));
}
const updatedOfficialRatings = existingOfficialRatings.includes(value) ?
existingOfficialRatings.filter((filter) => filter !== value) :
[...existingOfficialRatings, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
OfficialRatings: updatedOfficialRatings.length ? updatedOfficialRatings : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.OfficialRatings]
);

View file

@ -25,27 +25,20 @@ const FiltersSeriesStatus: FC<FiltersSeriesStatusProps> = ({
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = event.target.value as SeriesStatus;
const existingValue = libraryViewSettings?.Filters?.SeriesStatus;
const existingSeriesStatus = 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]
}
}));
}
const updatedSeriesStatus = existingSeriesStatus.includes(value) ?
existingSeriesStatus.filter((filter) => filter !== value) :
[...existingSeriesStatus, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
SeriesStatus: updatedSeriesStatus.length ? updatedSeriesStatus : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.SeriesStatus]
);

View file

@ -29,27 +29,20 @@ const FiltersStatus: FC<FiltersStatusProps> = ({
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = event.target.value as ItemFilter;
const existingValue = libraryViewSettings?.Filters?.Status;
const existingStatus = 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]
}
}));
}
const updatedStatus = existingStatus.includes(value) ?
existingStatus.filter((filter) => filter !== value) :
[...existingStatus, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
Status: updatedStatus.length ? updatedStatus : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.Status]
);

View file

@ -19,30 +19,23 @@ const FiltersStudios: FC<FiltersStudiosProps> = ({
const onFiltersStudiosChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = String(event.target.value);
const existingValue = libraryViewSettings?.Filters?.StudioIds;
const value = event.target.value;
const existingStudioIds = 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]
}
}));
}
const updatedStudioIds = existingStudioIds.includes(value) ?
existingStudioIds.filter((filter) => filter !== value) :
[...existingStudioIds, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
StudioIds: updatedStudioIds.length ? updatedStudioIds : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.StudioIds]
[setLibraryViewSettings, libraryViewSettings.Filters?.StudioIds]
);
return (

View file

@ -19,30 +19,23 @@ const FiltersTags: FC<FiltersTagsProps> = ({
const onFiltersTagsChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = String(event.target.value);
const existingValue = libraryViewSettings?.Filters?.Tags;
const value = event.target.value;
const existingTags = 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]
}
}));
}
const updatedTags = existingTags.includes(value) ?
existingTags.filter((filter) => filter !== value) :
[...existingTags, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
Tags: updatedTags.length ? updatedTags : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.Tags]
[setLibraryViewSettings, libraryViewSettings.Filters?.Tags]
);
return (

View file

@ -2,9 +2,15 @@ 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 { LibraryViewSettings, VideoBasicFilter } from 'types/library';
import { VideoType } from '@jellyfin/sdk/lib/generated-client';
import globalize from 'scripts/globalize';
const videoBasicFilterOptions = [
{ label: 'SD', value: VideoBasicFilter.IsSD },
{ label: 'HD', value: VideoBasicFilter.IsHD },
{ label: '4K', value: VideoBasicFilter.Is4K },
{ label: '3D', value: VideoBasicFilter.Is3D }
];
const videoTypesOptions = [
{ label: 'DVD', value: VideoType.Dvd },
@ -21,93 +27,67 @@ const FiltersVideoTypes: FC<FiltersVideoTypesProps> = ({
libraryViewSettings,
setLibraryViewSettings
}) => {
const onFiltersvideoStandardChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = event.target.value as VideoBasicFilter;
const existingVideoBasicFilter = libraryViewSettings?.Filters?.VideoBasicFilter ?? [];
const updatedVideoBasicFilter = existingVideoBasicFilter.includes(value) ?
existingVideoBasicFilter.filter((filter) => filter !== value) :
[...existingVideoBasicFilter, value];
setLibraryViewSettings((prevState) => ({
...prevState,
Filters: {
...prevState.Filters,
VideoBasicFilter: updatedVideoBasicFilter.length ? updatedVideoBasicFilter : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.VideoBasicFilter]
);
const onFiltersVideoTypesChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = event.target.value as VideoType;
const existingValue = libraryViewSettings?.Filters?.VideoTypes;
const existingVideoTypes = 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]
}
}));
}
const updatedVideoTypes = existingVideoTypes.includes(value) ?
existingVideoTypes.filter((filter) => filter !== value) :
[...existingVideoTypes, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
VideoTypes: updatedVideoTypes.length ? updatedVideoTypes : undefined
}
}));
},
[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
{videoBasicFilterOptions
.map((filter) => (
<FormControlLabel
key={filter.value}
control={
<Checkbox
checked={
!!libraryViewSettings?.Filters?.VideoBasicFilter?.includes(filter.value)
}
onChange={onFiltersvideoStandardChange}
value={filter.value}
/>
}
onChange={handleChange}
name='Is4K'
label={filter.label}
/>
}
label={globalize.translate('4K')}
/>
<FormControlLabel
control={
<Checkbox
checked={
libraryViewSettings.Is3D
}
onChange={handleChange}
name='Is3D'
/>
}
label={globalize.translate('3D')}
/>
))}
{videoTypesOptions
.map((filter) => (
<FormControlLabel

View file

@ -20,27 +20,20 @@ const FiltersYears: FC<FiltersYearsProps> = ({
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const value = Number(event.target.value);
const existingValue = libraryViewSettings?.Filters?.Years;
const existingYears = 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]
}
}));
}
const updatedYears = existingYears.includes(value) ?
existingYears.filter((filter) => filter !== value) :
[...existingYears, value];
setLibraryViewSettings((prevState) => ({
...prevState,
StartIndex: 0,
Filters: {
...prevState.Filters,
Years: updatedYears.length ? updatedYears : undefined
}
}));
},
[setLibraryViewSettings, libraryViewSettings?.Filters?.Years]
);

View file

@ -2,8 +2,8 @@ import type { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-
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 type { ImageType } from '@jellyfin/sdk/lib/generated-client';
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
import { ImageType } from '@jellyfin/sdk/lib/generated-client';
export type ParentId = string | null | undefined;
@ -11,15 +11,38 @@ export interface LibraryViewProps {
parentId: string | null;
}
interface Filters {
Features?: string[];
export enum FeatureFilters {
HasSubtitles = 'HasSubtitles',
HasTrailer = 'HasTrailer',
HasSpecialFeature = 'HasSpecialFeature',
HasThemeSong = 'HasThemeSong',
HasThemeVideo = 'HasThemeVideo',
}
export enum EpisodeFilter {
ParentIndexNumber = 'ParentIndexNumber',
IsMissing = 'IsMissing',
IsUnaired = 'IsUnaired',
}
export enum VideoBasicFilter {
IsSD = 'IsSD',
IsHD = 'IsHD',
Is4K = 'Is4K',
Is3D = 'Is3D',
}
export interface Filters {
Features?: FeatureFilters[];
Genres?: string[];
OfficialRatings?: string[];
EpisodeFilter?: EpisodeFilter[];
Status?: ItemFilter[];
EpisodesStatus?: string[];
SeriesStatus?: SeriesStatus[];
StudioIds?: string[];
Tags?: string[];
VideoBasicFilter?: VideoBasicFilter[];
VideoTypes?: VideoType[];
Years?: number[];
}
@ -39,10 +62,6 @@ export interface LibraryViewSettings {
ShowTitle: boolean;
ShowYear?: boolean;
Filters?: Filters;
IsSD?: boolean;
IsHD?: boolean;
Is4K?: boolean;
Is3D?: boolean;
NameLessThan?: string | null;
NameStartsWith?: string | null;
}