Progress on advanced filters

This commit is contained in:
jeffvli
2022-11-03 03:25:10 -07:00
parent be05c1df79
commit 53a7d728b3
3 changed files with 269 additions and 110 deletions
+66 -53
View File
@@ -99,6 +99,21 @@ export type AdvancedFilterGroup = {
uniqueId: string; uniqueId: string;
}; };
const operatorMap = {
'!=': 'not',
'!~': 'contains',
$: 'endsWith',
'<': 'lt',
'<=': 'lte',
'=': 'equals',
'>': 'gt',
'>=': 'gte',
'^': 'startsWith',
'~': 'contains',
};
const insensitiveFields = ['name'];
const advancedFilterGroup = ( const advancedFilterGroup = (
groups: AdvancedFilterGroup[], groups: AdvancedFilterGroup[],
user: AuthUser, user: AuthUser,
@@ -119,57 +134,65 @@ const advancedFilterGroup = (
for (const rule of group.rules) { for (const rule of group.rules) {
if (rule.field && rule.operator) { if (rule.field && rule.operator) {
const [table, field, relationField] = rule.field.split('.'); const [table, field, relationField] = rule.field.split('.');
const condition = rule.operator === '!~' ? 'none' : 'some';
const op = operatorMap[rule.operator as keyof typeof operatorMap];
switch (table) {
case 'albums':
if (field === 'ratings') {
query[rootType].push({
[field]: {
[condition]: {
[relationField]: {
[op]: rule.value,
},
userId: user.id,
},
},
});
break;
}
if (field === 'ratings') {
if (table === 'albums') {
query[rootType].push({ query[rootType].push({
[field]: { [field]: {
some: { mode: insensitiveFields.includes(field)
[relationField]: { ? 'insensitive'
[rule.operator]: rule.value, : undefined,
}, [op]: rule.value,
userId: user.id,
},
}, },
}); });
} else { break;
query[rootType].push({
[table]: { default:
some: { if (field === 'ratings') {
[field]: { query[rootType].push({
some: { [table]: {
[relationField]: { some: {
[rule.operator]: rule.value, [field]: {
some: {
[relationField]: {
[op]: rule.value,
},
userId: user.id,
}, },
userId: user.id,
}, },
}, },
}, },
}, });
}); break;
} }
} else if (table === 'albums') {
const obj = {
[field]: {
[rule.operator]: rule.value,
mode: 'insensitive',
},
};
query[rootType].push(obj); query[rootType].push({
} else { [table]: {
const obj = { [condition]: {
[table]: { [field]: {
some: { mode: 'insensitive',
[field]: { [op]: rule.value,
[rule.operator]: rule.value, },
mode: 'insensitive',
}, },
}, },
}, });
}; break;
query[rootType].push(obj);
} }
} }
} }
@@ -192,19 +215,6 @@ const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
[rootQueryType]: [] as any[], [rootQueryType]: [] as any[],
}; };
const operatorMap = {
'!=': 'not',
'!~': 'contains',
$: 'endsWith',
'<': 'lt',
'<=': 'lte',
'=': 'equals',
'>': 'gt',
'>=': 'gte',
'^': 'startsWith',
'~': 'contains',
};
for (const rule of filter.rules) { for (const rule of filter.rules) {
if (rule.field && rule.operator) { if (rule.field && rule.operator) {
let [table, field, relationField] = rule.field.split('.'); let [table, field, relationField] = rule.field.split('.');
@@ -226,9 +236,12 @@ const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
}); });
break; break;
} }
rootQuery[rootQueryType].push({ rootQuery[rootQueryType].push({
[field]: { [field]: {
mode: 'insensitive', mode: insensitiveFields.includes(field)
? 'insensitive'
: undefined,
[op]: rule.value, [op]: rule.value,
}, },
}); });
@@ -1,4 +1,4 @@
import { Box, Stack, Group } from '@mantine/core'; import { Stack, Group } from '@mantine/core';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import get from 'lodash/get'; import get from 'lodash/get';
import set from 'lodash/set'; import set from 'lodash/set';
@@ -75,7 +75,7 @@ const FILTER_GROUP_OPTIONS_DATA = [
const FILTER_OPTIONS_DATA = [ const FILTER_OPTIONS_DATA = [
{ {
label: 'Artist Name', label: 'Artist Title',
value: 'artists.name', value: 'artists.name',
}, },
{ {
@@ -87,7 +87,7 @@ const FILTER_OPTIONS_DATA = [
value: 'artists.genre', value: 'artists.genre',
}, },
{ {
label: 'Album Artist Name', label: 'Album Artist Title',
value: 'albumArtists.name', value: 'albumArtists.name',
}, },
{ {
@@ -99,7 +99,7 @@ const FILTER_OPTIONS_DATA = [
value: 'albumArtists.genre', value: 'albumArtists.genre',
}, },
{ {
label: 'Album Name', label: 'Album Title',
value: 'albums.name', value: 'albums.name',
}, },
{ {
@@ -112,7 +112,7 @@ const FILTER_OPTIONS_DATA = [
}, },
{ {
label: 'Album Year', label: 'Album Year',
value: 'albums.year', value: 'albums.releaseYear',
}, },
{ {
label: 'Album Release Date', label: 'Album Release Date',
@@ -127,7 +127,7 @@ const FILTER_OPTIONS_DATA = [
value: 'albums.dateAdded', value: 'albums.dateAdded',
}, },
{ {
label: 'Track Name', label: 'Track Title',
value: 'songs.name', value: 'songs.name',
}, },
{ {
@@ -171,7 +171,7 @@ const OPTIONS_MAP = {
'albums.releaseDate': { 'albums.releaseDate': {
type: 'date', type: 'date',
}, },
'albums.year': { 'albums.releaseYear': {
type: 'number', type: 'number',
}, },
'artists.genre': { 'artists.genre': {
@@ -194,10 +194,47 @@ const OPTIONS_MAP = {
}, },
}; };
export const formatAdvancedFiltersGroups = (groups: AdvancedFilterGroup[]) => {
const filterGroups: any[] = [];
for (const group of groups) {
const rules = group.rules
.filter((rule) => rule.field && rule.operator && rule.value)
.map((rule) => ({ ...rule, uniqueId: undefined }));
const updatedGroup = { ...group, rules, uniqueId: undefined };
if (group.group.length > 0) {
const nestedRuleGroup = formatAdvancedFiltersGroups(group.group);
nestedRuleGroup.forEach((group) => groups.push(group));
}
if (updatedGroup.rules.length > 0) {
filterGroups.push(updatedGroup);
}
}
return filterGroups;
};
// Prevent query key from constantly changing due to empty rules or groups
export const formatAdvancedFiltersQuery = (filter: AdvancedFilterGroup) => {
const updatedFilter = {
...filter,
group: formatAdvancedFiltersGroups(filter.group),
rules: filter.rules
.filter((rule) => rule.field && rule.operator && rule.value)
.map((rule) => ({ ...rule, uniqueId: undefined })),
};
return updatedFilter;
};
interface FilterOptionProps { interface FilterOptionProps {
data: AdvancedFilterRule; data: AdvancedFilterRule;
groupIndex: number[]; groupIndex: number[];
level: number; level: number;
noRemove: boolean;
onChangeField: (args: any) => void; onChangeField: (args: any) => void;
onChangeOperator: (args: any) => void; onChangeOperator: (args: any) => void;
onChangeValue: (args: any) => void; onChangeValue: (args: any) => void;
@@ -209,6 +246,7 @@ const FilterOption = ({
level, level,
onDeleteRule, onDeleteRule,
groupIndex, groupIndex,
noRemove,
onChangeField, onChangeField,
onChangeOperator, onChangeOperator,
onChangeValue, onChangeValue,
@@ -254,37 +292,45 @@ const FilterOption = ({
const filterOperatorMap = { const filterOperatorMap = {
date: ( date: (
<Select <Select
searchable
data={DATE_FILTER_OPTIONS_DATA} data={DATE_FILTER_OPTIONS_DATA}
maxWidth={175}
size="xs" size="xs"
value={operator} value={operator}
width={150} width="20%"
onChange={handleChangeOperator} onChange={handleChangeOperator}
/> />
), ),
id: ( id: (
<Select <Select
searchable
data={ID_FILTER_OPTIONS_DATA} data={ID_FILTER_OPTIONS_DATA}
maxWidth={175}
size="xs" size="xs"
value={operator} value={operator}
width={150} width="20%"
onChange={handleChangeOperator} onChange={handleChangeOperator}
/> />
), ),
number: ( number: (
<Select <Select
searchable
data={NUMBER_FILTER_OPTIONS_DATA} data={NUMBER_FILTER_OPTIONS_DATA}
maxWidth={175}
size="xs" size="xs"
value={operator} value={operator}
width={150} width="20%"
onChange={handleChangeOperator} onChange={handleChangeOperator}
/> />
), ),
string: ( string: (
<Select <Select
searchable
data={STRING_FILTER_OPTIONS_DATA} data={STRING_FILTER_OPTIONS_DATA}
maxWidth={175}
size="xs" size="xs"
value={operator} value={operator}
width={150} width="20%"
onChange={handleChangeOperator} onChange={handleChangeOperator}
/> />
), ),
@@ -295,27 +341,30 @@ const FilterOption = ({
<Select <Select
searchable searchable
data={[]} data={[]}
maxWidth={175}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'albumArtists.name': ( 'albumArtists.name': (
<TextInput <TextInput
maxWidth={175}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'albumArtists.ratings.value': ( 'albumArtists.ratings.value': (
<NumberInput <NumberInput
max={5} max={5}
maxWidth={175}
min={0} min={0}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
@@ -323,10 +372,11 @@ const FilterOption = ({
<DatePicker <DatePicker
initialLevel="year" initialLevel="year"
maxDate={dayjs(new Date()).year(3000).toDate()} maxDate={dayjs(new Date()).year(3000).toDate()}
maxWidth={175}
minDate={dayjs(new Date()).year(1950).toDate()} minDate={dayjs(new Date()).year(1950).toDate()}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
@@ -334,36 +384,40 @@ const FilterOption = ({
<Select <Select
searchable searchable
data={[]} data={[]}
maxWidth={175}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'albums.name': ( 'albums.name': (
<TextInput <TextInput
maxWidth={175}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'albums.playCount': ( 'albums.playCount': (
<NumberInput <NumberInput
maxWidth={175}
min={0} min={0}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={(e) => handleChangeValue(e)} onChange={(e) => handleChangeValue(e)}
/> />
), ),
'albums.ratings.value': ( 'albums.ratings.value': (
<NumberInput <NumberInput
max={5} max={5}
maxWidth={175}
min={0} min={0}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
@@ -371,19 +425,21 @@ const FilterOption = ({
<DatePicker <DatePicker
initialLevel="year" initialLevel="year"
maxDate={dayjs(new Date()).year(3000).toDate()} maxDate={dayjs(new Date()).year(3000).toDate()}
maxWidth={175}
minDate={dayjs(new Date()).year(1950).toDate()} minDate={dayjs(new Date()).year(1950).toDate()}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'albums.year': ( 'albums.releaseYear': (
<NumberInput <NumberInput
maxWidth={175}
min={0} min={0}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
@@ -391,60 +447,75 @@ const FilterOption = ({
<Select <Select
searchable searchable
data={[]} data={[]}
maxWidth={175}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'artists.name': ( 'artists.name': (
<TextInput <TextInput
maxWidth={175}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'artists.ratings.value': ( 'artists.ratings.value': (
<NumberInput <NumberInput
max={5} max={5}
maxWidth={175}
min={0} min={0}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'songs.name': ( 'songs.name': (
<TextInput size="xs" width={150} onChange={handleChangeValue} /> <TextInput
maxWidth={175}
size="xs"
width="20%"
onChange={handleChangeValue}
/>
), ),
'songs.playCount': ( 'songs.playCount': (
<NumberInput <NumberInput
maxWidth={175}
min={0} min={0}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
'songs.ratings.value': ( 'songs.ratings.value': (
<NumberInput <NumberInput
max={5} max={5}
maxWidth={175}
min={0} min={0}
size="xs" size="xs"
value={value} value={value}
width={150} width="20%"
onChange={handleChangeValue} onChange={handleChangeValue}
/> />
), ),
}; };
const ml = (level + 1) * 10 - level * 5;
return ( return (
<Group ml={`${(level + 1) * 10}px`}> <Group ml={ml}>
<Select <Select
searchable
data={FILTER_OPTIONS_DATA} data={FILTER_OPTIONS_DATA}
maxWidth={175}
size="xs" size="xs"
value={field} value={field}
width="20%"
onChange={handleChangeField} onChange={handleChangeField}
/> />
{field ? ( {field ? (
@@ -453,14 +524,15 @@ const FilterOption = ({
.type as keyof typeof filterOperatorMap .type as keyof typeof filterOperatorMap
] ]
) : ( ) : (
<TextInput disabled size="xs" width={150} /> <TextInput disabled maxWidth={175} size="xs" width="20%" />
)} )}
{field ? ( {field ? (
filterInputValueMap[field as keyof typeof filterInputValueMap] filterInputValueMap[field as keyof typeof filterInputValueMap]
) : ( ) : (
<TextInput disabled size="xs" width={150} /> <TextInput disabled maxWidth={175} size="xs" width="20%" />
)} )}
<Button <Button
disabled={noRemove}
px={5} px={5}
size="xs" size="xs"
tooltip={{ label: 'Remove rule' }} tooltip={{ label: 'Remove rule' }}
@@ -533,9 +605,12 @@ const FilterGroup = ({
<Stack ml={`${level * 10}px`}> <Stack ml={`${level * 10}px`}>
<Group> <Group>
<Select <Select
searchable
data={FILTER_GROUP_OPTIONS_DATA} data={FILTER_GROUP_OPTIONS_DATA}
maxWidth={175}
size="xs" size="xs"
value={data.type} value={data.type}
width="20%"
onChange={handleChangeType} onChange={handleChangeType}
/> />
<Button <Button
@@ -571,6 +646,7 @@ const FilterGroup = ({
data={rule} data={rule}
groupIndex={groupIndex || []} groupIndex={groupIndex || []}
level={level} level={level}
noRemove={data.rules.length === 1}
onChangeField={onChangeField} onChangeField={onChangeField}
onChangeOperator={onChangeOperator} onChangeOperator={onChangeOperator}
onChangeValue={onChangeValue} onChangeValue={onChangeValue}
@@ -804,7 +880,7 @@ export const AdvancedFilters = ({ filters, setFilters }: any) => {
}; };
return ( return (
<Box m={10}> <>
<FilterGroup <FilterGroup
data={filters} data={filters}
groupIndex={[]} groupIndex={[]}
@@ -819,6 +895,6 @@ export const AdvancedFilters = ({ filters, setFilters }: any) => {
onDeleteRule={handleDeleteRule} onDeleteRule={handleDeleteRule}
onDeleteRuleGroup={handleDeleteRuleGroup} onDeleteRuleGroup={handleDeleteRuleGroup}
/> />
</Box> </>
); );
}; };
@@ -1,10 +1,10 @@
/* eslint-disable no-plusplus */ /* eslint-disable no-plusplus */
import { useState, useCallback, useMemo } from 'react'; import { useState, useCallback, useMemo } from 'react';
import { Group, Checkbox } from '@mantine/core'; import { Group, Checkbox } from '@mantine/core';
import { useDebouncedValue, useSetState } from '@mantine/hooks'; import { useDebouncedValue, useSetState, useToggle } from '@mantine/hooks';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { RiArrowDownSLine } from 'react-icons/ri'; import { RiArrowDownSLine, RiArrowLeftLine } from 'react-icons/ri';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { api } from '@/renderer/api'; import { api } from '@/renderer/api';
import { AlbumSort } from '@/renderer/api/albums.api'; import { AlbumSort } from '@/renderer/api/albums.api';
@@ -13,6 +13,9 @@ import { SortOrder } from '@/renderer/api/types';
import { import {
Button, Button,
DropdownMenu, DropdownMenu,
NumberInput,
ScrollArea,
Paper,
Text, Text,
VirtualGridAutoSizerContainer, VirtualGridAutoSizerContainer,
VirtualGridContainer, VirtualGridContainer,
@@ -22,13 +25,13 @@ import {
AdvancedFilterGroup, AdvancedFilterGroup,
AdvancedFilters, AdvancedFilters,
FilterGroupType, FilterGroupType,
formatAdvancedFiltersQuery,
} from '@/renderer/features/albums/components/advanced-filters'; } from '@/renderer/features/albums/components/advanced-filters';
import { useAlbumList } from '@/renderer/features/albums/queries/use-album-list'; import { useAlbumList } from '@/renderer/features/albums/queries/use-album-list';
import { useServerList } from '@/renderer/features/servers'; import { useServerList } from '@/renderer/features/servers';
import { AnimatedPage, useServerCredential } from '@/renderer/features/shared'; import { AnimatedPage, useServerCredential } from '@/renderer/features/shared';
import { AppRoute } from '@/renderer/router/routes'; import { AppRoute } from '@/renderer/router/routes';
import { useAuthStore } from '@/renderer/store'; import { useAuthStore } from '@/renderer/store';
import { Font } from '@/renderer/styles';
import { LibraryItem } from '@/renderer/types'; import { LibraryItem } from '@/renderer/types';
import { import {
ViewType, ViewType,
@@ -36,20 +39,20 @@ import {
} from '../../library/components/ViewTypeButton'; } from '../../library/components/ViewTypeButton';
const FILTERS = [ const FILTERS = [
{ name: 'Title', value: AlbumSort.NAME },
{ name: 'Date added', value: AlbumSort.DATE_ADDED }, { name: 'Date added', value: AlbumSort.DATE_ADDED },
{ {
name: 'Date added (remote)', name: 'Date Added (remote)',
value: AlbumSort.DATE_ADDED_REMOTE, value: AlbumSort.DATE_ADDED_REMOTE,
}, },
{ name: 'Date released', value: AlbumSort.DATE_RELEASED }, { name: 'Release Date', value: AlbumSort.DATE_RELEASED },
{ name: 'Favorites', value: AlbumSort.FAVORITE },
{ name: 'Random', value: AlbumSort.RANDOM },
{ name: 'Rating', value: AlbumSort.RATING },
{ name: 'Title', value: AlbumSort.NAME },
{ name: 'Year', value: AlbumSort.DATE_RELEASED_YEAR }, { name: 'Year', value: AlbumSort.DATE_RELEASED_YEAR },
{ name: 'Random', value: AlbumSort.RANDOM },
{ name: 'Favorites', value: AlbumSort.FAVORITE },
{ name: 'Rating', value: AlbumSort.RATING },
]; ];
const SORT = [ const ORDER = [
{ name: 'Ascending', value: SortOrder.ASC }, { name: 'Ascending', value: SortOrder.ASC },
{ name: 'Descending', value: SortOrder.DESC }, { name: 'Descending', value: SortOrder.DESC },
]; ];
@@ -66,15 +69,20 @@ export const AlbumListRoute = () => {
sortBy: AlbumSort.NAME, sortBy: AlbumSort.NAME,
}); });
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilterGroup>({ const [isAdvFilter, toggleAdvFilter] = useToggle();
const [rawAdvFilters, setRawAdvFilters] = useState<AdvancedFilterGroup>({
group: [], group: [],
rules: [{ field: null, operator: null, uniqueId: nanoid(), value: null }], rules: [{ field: null, operator: null, uniqueId: nanoid(), value: null }],
type: FilterGroupType.AND, type: FilterGroupType.AND,
uniqueId: nanoid(), uniqueId: nanoid(),
}); });
const [debouncedFilters] = useDebouncedValue(advancedFilters, 300); const [debouncedAdvFilters] = useDebouncedValue(rawAdvFilters, 300);
const encoded = encodeURI(JSON.stringify(debouncedFilters));
const advancedFilters = useMemo(() => {
const value = formatAdvancedFiltersQuery(debouncedAdvFilters);
return encodeURI(JSON.stringify(value));
}, [debouncedAdvFilters]);
const serverFolders = useMemo(() => { const serverFolders = useMemo(() => {
const server = servers?.data.find((server) => server.id === serverId); const server = servers?.data.find((server) => server.id === serverId);
@@ -82,7 +90,7 @@ export const AlbumListRoute = () => {
}, [serverId, servers]); }, [serverId, servers]);
const { data: albums } = useAlbumList({ const { data: albums } = useAlbumList({
advancedFilters: encoded, advancedFilters,
orderBy: filters.orderBy, orderBy: filters.orderBy,
serverFolderId: filters.serverFolderId, serverFolderId: filters.serverFolderId,
skip: 0, skip: 0,
@@ -97,12 +105,12 @@ export const AlbumListRoute = () => {
skip, skip,
take, take,
...filters, ...filters,
advancedFilters: encoded, advancedFilters,
}), }),
async () => async () =>
api.albums.getAlbumList( api.albums.getAlbumList(
{ serverId }, { serverId },
{ skip, take, ...filters, advancedFilters: encoded } { skip, take, ...filters, advancedFilters }
) )
); );
@@ -120,7 +128,14 @@ export const AlbumListRoute = () => {
return albums; return albums;
}, },
[encoded, filters, isImageTokenRequired, queryClient, serverId, serverToken] [
advancedFilters,
filters,
isImageTokenRequired,
queryClient,
serverId,
serverToken,
]
); );
return ( return (
@@ -128,7 +143,7 @@ export const AlbumListRoute = () => {
<VirtualGridContainer> <VirtualGridContainer>
<Group m={10} position="apart"> <Group m={10} position="apart">
<Group> <Group>
<Text font={Font.POPPINS} size="lg"> <Text noSelect size="lg">
Albums Albums
</Text> </Text>
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
@@ -144,26 +159,54 @@ export const AlbumListRoute = () => {
{FILTERS.map((filter) => ( {FILTERS.map((filter) => (
<DropdownMenu.Item <DropdownMenu.Item
key={`filter-${filter.value}`} key={`filter-${filter.value}`}
color={
filter.value === filters.sortBy
? 'var(--primary-color)'
: undefined
}
rightSection={
filter.value === filters.sortBy ? (
<RiArrowLeftLine />
) : undefined
}
onClick={() => setFilters({ sortBy: filter.value })} onClick={() => setFilters({ sortBy: filter.value })}
> >
{filter.name} {filter.name}
</DropdownMenu.Item> </DropdownMenu.Item>
))} ))}
<DropdownMenu.Divider />
<DropdownMenu.Item
color={isAdvFilter ? 'var(--primary-color)' : undefined}
rightSection={isAdvFilter ? <RiArrowLeftLine /> : undefined}
onClick={() => toggleAdvFilter()}
>
Advanced Filters
</DropdownMenu.Item>
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button compact variant="subtle"> <Button compact variant="subtle">
<Group> <Group>
{SORT.find((s) => s.value === filters.orderBy)?.name}{' '} {ORDER.find((s) => s.value === filters.orderBy)?.name}{' '}
<RiArrowDownSLine size={15} /> <RiArrowDownSLine size={15} />
</Group> </Group>
</Button> </Button>
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
{SORT.map((sort) => ( {ORDER.map((sort) => (
<DropdownMenu.Item <DropdownMenu.Item
key={`sort-${sort.value}`} key={`sort-${sort.value}`}
color={
sort.value === filters.orderBy
? 'var(--primary-color)'
: undefined
}
rightSection={
sort.value === filters.orderBy ? (
<RiArrowLeftLine />
) : undefined
}
onClick={() => setFilters({ orderBy: sort.value })} onClick={() => setFilters({ orderBy: sort.value })}
> >
{sort.name} {sort.name}
@@ -204,10 +247,37 @@ export const AlbumListRoute = () => {
/> />
</Group> </Group>
</Group> </Group>
<AdvancedFilters {isAdvFilter && (
filters={advancedFilters} <>
setFilters={setAdvancedFilters} <Paper sx={{ maxHeight: '20vh' }}>
/> <ScrollArea
my={10}
px={10}
sx={{ height: '100%', width: '100%' }}
>
<Group noWrap my={10} position="apart">
<Group>
<Text>Advanced Filters</Text>
<NumberInput
disabled
min={1}
placeholder="Limit"
size="xs"
width={75}
/>
</Group>
<Button disabled uppercase>
Save as...
</Button>
</Group>
<AdvancedFilters
filters={rawAdvFilters}
setFilters={setRawAdvFilters}
/>
</ScrollArea>
</Paper>
</>
)}
<VirtualGridAutoSizerContainer> <VirtualGridAutoSizerContainer>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (