warn if a value in select no longer exists

This commit is contained in:
Kendall Garner 2025-05-18 10:59:45 -07:00
parent f068d6e4b8
commit cf74625bfc
No known key found for this signature in database
GPG key ID: 9355F387FE765C94
6 changed files with 93 additions and 11 deletions

View file

@ -160,6 +160,7 @@
"audioDeviceFetchError": "an error occurred when trying to get audio devices", "audioDeviceFetchError": "an error occurred when trying to get audio devices",
"authenticationFailed": "authentication failed", "authenticationFailed": "authentication failed",
"badAlbum": "you are seeing this page because this song is not part of an album. you are most likely seeing this issue if you have a song at the top level of your music folder. jellyfin only groups tracks if they are in a folder.", "badAlbum": "you are seeing this page because this song is not part of an album. you are most likely seeing this issue if you have a song at the top level of your music folder. jellyfin only groups tracks if they are in a folder.",
"badValue": "invalid option \"{{value}}\". this value no longer exists",
"credentialsRequired": "credentials required", "credentialsRequired": "credentials required",
"endpointNotImplementedError": "endpoint {{endpoint}} is not implemented for {{serverType}}", "endpointNotImplementedError": "endpoint {{endpoint}} is not implemented for {{serverType}}",
"genericError": "an error occurred", "genericError": "an error occurred",

View file

@ -0,0 +1,78 @@
import { MultiSelect, MultiSelectProps, Select, SelectProps } from '/@/renderer/components/select';
import { useTranslation } from 'react-i18next';
import { useMemo } from 'react';
export const SelectWithInvalidData = ({ data, defaultValue, ...props }: SelectProps) => {
const { t } = useTranslation();
const [fullData, hasError] = useMemo(() => {
if (typeof defaultValue === 'string') {
const missingField =
data.find((item) =>
typeof item === 'string' ? item === defaultValue : item.value === defaultValue,
) === undefined;
if (missingField) {
return [data.concat(defaultValue), true];
}
}
return [data, false];
}, [data, defaultValue]);
return (
<Select
data={fullData}
defaultValue={defaultValue}
error={
hasError
? t('error.badValue', { postProcess: 'sentenceCase', value: defaultValue })
: undefined
}
{...props}
/>
);
};
export const MultiSelectWithInvalidData = ({ data, defaultValue, ...props }: MultiSelectProps) => {
const { t } = useTranslation();
const [fullData, missing] = useMemo(() => {
if (defaultValue?.length) {
const validValues = new Set<string>();
for (const item of data) {
if (typeof item === 'string') {
validValues.add(item);
} else {
validValues.add(item.value);
}
}
const missingFields: string[] = [];
for (const value of defaultValue) {
if (!validValues.has(value)) {
missingFields.push(value);
}
}
if (missingFields.length > 0) {
return [data.concat(missingFields), missingFields];
}
}
return [data, []];
}, [data, defaultValue]);
return (
<MultiSelect
data={fullData}
defaultValue={defaultValue}
error={
missing.length
? t('error.badValue', { postProcess: 'sentenceCase', value: missing })
: undefined
}
{...props}
/>
);
};

View file

@ -5,7 +5,7 @@ import type {
import { Select as MantineSelect, MultiSelect as MantineMultiSelect } from '@mantine/core'; import { Select as MantineSelect, MultiSelect as MantineMultiSelect } from '@mantine/core';
import styled from 'styled-components'; import styled from 'styled-components';
interface SelectProps extends MantineSelectProps { export interface SelectProps extends MantineSelectProps {
maxWidth?: number | string; maxWidth?: number | string;
width?: number | string; width?: number | string;
} }

View file

@ -1,6 +1,6 @@
import { ChangeEvent, useMemo, useState } from 'react'; import { ChangeEvent, useMemo, useState } from 'react';
import { Divider, Group, Stack } from '@mantine/core'; import { Divider, Group, Stack } from '@mantine/core';
import { NumberInput, Switch, Text, Select, SpinnerIcon } from '/@/renderer/components'; import { NumberInput, Switch, Text, SpinnerIcon } from '/@/renderer/components';
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store'; import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useGenreList } from '/@/renderer/features/genres'; import { useGenreList } from '/@/renderer/features/genres';
@ -14,6 +14,7 @@ import {
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list'; import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
import { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
interface NavidromeAlbumFiltersProps { interface NavidromeAlbumFiltersProps {
customFilters?: Partial<AlbumListFilter>; customFilters?: Partial<AlbumListFilter>;
@ -251,7 +252,7 @@ export const NavidromeAlbumFilters = ({
min={0} min={0}
onChange={(e) => handleYearFilter(e)} onChange={(e) => handleYearFilter(e)}
/> />
<Select <SelectWithInvalidData
clearable clearable
searchable searchable
data={genreList} data={genreList}
@ -261,7 +262,7 @@ export const NavidromeAlbumFilters = ({
/> />
</Group> </Group>
<Group grow> <Group grow>
<Select <SelectWithInvalidData
clearable clearable
searchable searchable
data={selectableAlbumArtists} data={selectableAlbumArtists}
@ -281,7 +282,7 @@ export const NavidromeAlbumFilters = ({
key={tag.name} key={tag.name}
grow grow
> >
<Select <SelectWithInvalidData
clearable clearable
searchable searchable
data={tag.options} data={tag.options}

View file

@ -2,11 +2,12 @@ import { ChangeEvent, useMemo } from 'react';
import { Divider, Group, Stack } from '@mantine/core'; import { Divider, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types'; import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types';
import { MultiSelect, NumberInput, Switch, Text } from '/@/renderer/components'; import { NumberInput, Switch, Text } from '/@/renderer/components';
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store'; import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list'; import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
import { useGenreList } from '/@/renderer/features/genres'; import { useGenreList } from '/@/renderer/features/genres';
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
interface JellyfinSongFiltersProps { interface JellyfinSongFiltersProps {
customFilters?: Partial<SongListFilter>; customFilters?: Partial<SongListFilter>;
@ -201,7 +202,7 @@ export const JellyfinSongFilters = ({
</Group> </Group>
{!isGenrePage && ( {!isGenrePage && (
<Group grow> <Group grow>
<MultiSelect <MultiSelectWithInvalidData
clearable clearable
searchable searchable
data={genreList} data={genreList}
@ -214,7 +215,7 @@ export const JellyfinSongFilters = ({
)} )}
{tagsQuery.data?.boolTags?.length && ( {tagsQuery.data?.boolTags?.length && (
<Group grow> <Group grow>
<MultiSelect <MultiSelectWithInvalidData
clearable clearable
searchable searchable
data={tagsQuery.data.boolTags} data={tagsQuery.data.boolTags}

View file

@ -2,11 +2,12 @@ import { ChangeEvent, useMemo } from 'react';
import { Divider, Group, Stack } from '@mantine/core'; import { Divider, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types'; import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types';
import { NumberInput, Select, Switch, Text } from '/@/renderer/components'; import { NumberInput, Switch, Text } from '/@/renderer/components';
import { useGenreList } from '/@/renderer/features/genres'; import { useGenreList } from '/@/renderer/features/genres';
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store'; import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list'; import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
import { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
interface NavidromeSongFiltersProps { interface NavidromeSongFiltersProps {
customFilters?: Partial<SongListFilter>; customFilters?: Partial<SongListFilter>;
@ -149,7 +150,7 @@ export const NavidromeSongFilters = ({
onChange={(e) => handleYearFilter(e)} onChange={(e) => handleYearFilter(e)}
/> />
{!isGenrePage && ( {!isGenrePage && (
<Select <SelectWithInvalidData
clearable clearable
searchable searchable
data={genreList} data={genreList}
@ -166,7 +167,7 @@ export const NavidromeSongFilters = ({
key={tag.name} key={tag.name}
grow grow
> >
<Select <SelectWithInvalidData
clearable clearable
searchable searchable
data={tag.options} data={tag.options}