mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-08 04:50:12 +02:00
Progress on advanced filters
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Box, Stack, Group } from '@mantine/core';
|
||||
import { Stack, Group } from '@mantine/core';
|
||||
import dayjs from 'dayjs';
|
||||
import get from 'lodash/get';
|
||||
import set from 'lodash/set';
|
||||
@@ -75,7 +75,7 @@ const FILTER_GROUP_OPTIONS_DATA = [
|
||||
|
||||
const FILTER_OPTIONS_DATA = [
|
||||
{
|
||||
label: 'Artist Name',
|
||||
label: 'Artist Title',
|
||||
value: 'artists.name',
|
||||
},
|
||||
{
|
||||
@@ -87,7 +87,7 @@ const FILTER_OPTIONS_DATA = [
|
||||
value: 'artists.genre',
|
||||
},
|
||||
{
|
||||
label: 'Album Artist Name',
|
||||
label: 'Album Artist Title',
|
||||
value: 'albumArtists.name',
|
||||
},
|
||||
{
|
||||
@@ -99,7 +99,7 @@ const FILTER_OPTIONS_DATA = [
|
||||
value: 'albumArtists.genre',
|
||||
},
|
||||
{
|
||||
label: 'Album Name',
|
||||
label: 'Album Title',
|
||||
value: 'albums.name',
|
||||
},
|
||||
{
|
||||
@@ -112,7 +112,7 @@ const FILTER_OPTIONS_DATA = [
|
||||
},
|
||||
{
|
||||
label: 'Album Year',
|
||||
value: 'albums.year',
|
||||
value: 'albums.releaseYear',
|
||||
},
|
||||
{
|
||||
label: 'Album Release Date',
|
||||
@@ -127,7 +127,7 @@ const FILTER_OPTIONS_DATA = [
|
||||
value: 'albums.dateAdded',
|
||||
},
|
||||
{
|
||||
label: 'Track Name',
|
||||
label: 'Track Title',
|
||||
value: 'songs.name',
|
||||
},
|
||||
{
|
||||
@@ -171,7 +171,7 @@ const OPTIONS_MAP = {
|
||||
'albums.releaseDate': {
|
||||
type: 'date',
|
||||
},
|
||||
'albums.year': {
|
||||
'albums.releaseYear': {
|
||||
type: 'number',
|
||||
},
|
||||
'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 {
|
||||
data: AdvancedFilterRule;
|
||||
groupIndex: number[];
|
||||
level: number;
|
||||
noRemove: boolean;
|
||||
onChangeField: (args: any) => void;
|
||||
onChangeOperator: (args: any) => void;
|
||||
onChangeValue: (args: any) => void;
|
||||
@@ -209,6 +246,7 @@ const FilterOption = ({
|
||||
level,
|
||||
onDeleteRule,
|
||||
groupIndex,
|
||||
noRemove,
|
||||
onChangeField,
|
||||
onChangeOperator,
|
||||
onChangeValue,
|
||||
@@ -254,37 +292,45 @@ const FilterOption = ({
|
||||
const filterOperatorMap = {
|
||||
date: (
|
||||
<Select
|
||||
searchable
|
||||
data={DATE_FILTER_OPTIONS_DATA}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={operator}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeOperator}
|
||||
/>
|
||||
),
|
||||
id: (
|
||||
<Select
|
||||
searchable
|
||||
data={ID_FILTER_OPTIONS_DATA}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={operator}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeOperator}
|
||||
/>
|
||||
),
|
||||
number: (
|
||||
<Select
|
||||
searchable
|
||||
data={NUMBER_FILTER_OPTIONS_DATA}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={operator}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeOperator}
|
||||
/>
|
||||
),
|
||||
string: (
|
||||
<Select
|
||||
searchable
|
||||
data={STRING_FILTER_OPTIONS_DATA}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={operator}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeOperator}
|
||||
/>
|
||||
),
|
||||
@@ -295,27 +341,30 @@ const FilterOption = ({
|
||||
<Select
|
||||
searchable
|
||||
data={[]}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'albumArtists.name': (
|
||||
<TextInput
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'albumArtists.ratings.value': (
|
||||
<NumberInput
|
||||
max={5}
|
||||
maxWidth={175}
|
||||
min={0}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
@@ -323,10 +372,11 @@ const FilterOption = ({
|
||||
<DatePicker
|
||||
initialLevel="year"
|
||||
maxDate={dayjs(new Date()).year(3000).toDate()}
|
||||
maxWidth={175}
|
||||
minDate={dayjs(new Date()).year(1950).toDate()}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
@@ -334,36 +384,40 @@ const FilterOption = ({
|
||||
<Select
|
||||
searchable
|
||||
data={[]}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'albums.name': (
|
||||
<TextInput
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'albums.playCount': (
|
||||
<NumberInput
|
||||
maxWidth={175}
|
||||
min={0}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={(e) => handleChangeValue(e)}
|
||||
/>
|
||||
),
|
||||
'albums.ratings.value': (
|
||||
<NumberInput
|
||||
max={5}
|
||||
maxWidth={175}
|
||||
min={0}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
@@ -371,19 +425,21 @@ const FilterOption = ({
|
||||
<DatePicker
|
||||
initialLevel="year"
|
||||
maxDate={dayjs(new Date()).year(3000).toDate()}
|
||||
maxWidth={175}
|
||||
minDate={dayjs(new Date()).year(1950).toDate()}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'albums.year': (
|
||||
'albums.releaseYear': (
|
||||
<NumberInput
|
||||
maxWidth={175}
|
||||
min={0}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
@@ -391,60 +447,75 @@ const FilterOption = ({
|
||||
<Select
|
||||
searchable
|
||||
data={[]}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'artists.name': (
|
||||
<TextInput
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'artists.ratings.value': (
|
||||
<NumberInput
|
||||
max={5}
|
||||
maxWidth={175}
|
||||
min={0}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'songs.name': (
|
||||
<TextInput size="xs" width={150} onChange={handleChangeValue} />
|
||||
<TextInput
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'songs.playCount': (
|
||||
<NumberInput
|
||||
maxWidth={175}
|
||||
min={0}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
'songs.ratings.value': (
|
||||
<NumberInput
|
||||
max={5}
|
||||
maxWidth={175}
|
||||
min={0}
|
||||
size="xs"
|
||||
value={value}
|
||||
width={150}
|
||||
width="20%"
|
||||
onChange={handleChangeValue}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
const ml = (level + 1) * 10 - level * 5;
|
||||
|
||||
return (
|
||||
<Group ml={`${(level + 1) * 10}px`}>
|
||||
<Group ml={ml}>
|
||||
<Select
|
||||
searchable
|
||||
data={FILTER_OPTIONS_DATA}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={field}
|
||||
width="20%"
|
||||
onChange={handleChangeField}
|
||||
/>
|
||||
{field ? (
|
||||
@@ -453,14 +524,15 @@ const FilterOption = ({
|
||||
.type as keyof typeof filterOperatorMap
|
||||
]
|
||||
) : (
|
||||
<TextInput disabled size="xs" width={150} />
|
||||
<TextInput disabled maxWidth={175} size="xs" width="20%" />
|
||||
)}
|
||||
{field ? (
|
||||
filterInputValueMap[field as keyof typeof filterInputValueMap]
|
||||
) : (
|
||||
<TextInput disabled size="xs" width={150} />
|
||||
<TextInput disabled maxWidth={175} size="xs" width="20%" />
|
||||
)}
|
||||
<Button
|
||||
disabled={noRemove}
|
||||
px={5}
|
||||
size="xs"
|
||||
tooltip={{ label: 'Remove rule' }}
|
||||
@@ -533,9 +605,12 @@ const FilterGroup = ({
|
||||
<Stack ml={`${level * 10}px`}>
|
||||
<Group>
|
||||
<Select
|
||||
searchable
|
||||
data={FILTER_GROUP_OPTIONS_DATA}
|
||||
maxWidth={175}
|
||||
size="xs"
|
||||
value={data.type}
|
||||
width="20%"
|
||||
onChange={handleChangeType}
|
||||
/>
|
||||
<Button
|
||||
@@ -571,6 +646,7 @@ const FilterGroup = ({
|
||||
data={rule}
|
||||
groupIndex={groupIndex || []}
|
||||
level={level}
|
||||
noRemove={data.rules.length === 1}
|
||||
onChangeField={onChangeField}
|
||||
onChangeOperator={onChangeOperator}
|
||||
onChangeValue={onChangeValue}
|
||||
@@ -804,7 +880,7 @@ export const AdvancedFilters = ({ filters, setFilters }: any) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box m={10}>
|
||||
<>
|
||||
<FilterGroup
|
||||
data={filters}
|
||||
groupIndex={[]}
|
||||
@@ -819,6 +895,6 @@ export const AdvancedFilters = ({ filters, setFilters }: any) => {
|
||||
onDeleteRule={handleDeleteRule}
|
||||
onDeleteRuleGroup={handleDeleteRuleGroup}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable no-plusplus */
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
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 { nanoid } from 'nanoid';
|
||||
import { RiArrowDownSLine } from 'react-icons/ri';
|
||||
import { RiArrowDownSLine, RiArrowLeftLine } from 'react-icons/ri';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { api } from '@/renderer/api';
|
||||
import { AlbumSort } from '@/renderer/api/albums.api';
|
||||
@@ -13,6 +13,9 @@ import { SortOrder } from '@/renderer/api/types';
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
NumberInput,
|
||||
ScrollArea,
|
||||
Paper,
|
||||
Text,
|
||||
VirtualGridAutoSizerContainer,
|
||||
VirtualGridContainer,
|
||||
@@ -22,13 +25,13 @@ import {
|
||||
AdvancedFilterGroup,
|
||||
AdvancedFilters,
|
||||
FilterGroupType,
|
||||
formatAdvancedFiltersQuery,
|
||||
} from '@/renderer/features/albums/components/advanced-filters';
|
||||
import { useAlbumList } from '@/renderer/features/albums/queries/use-album-list';
|
||||
import { useServerList } from '@/renderer/features/servers';
|
||||
import { AnimatedPage, useServerCredential } from '@/renderer/features/shared';
|
||||
import { AppRoute } from '@/renderer/router/routes';
|
||||
import { useAuthStore } from '@/renderer/store';
|
||||
import { Font } from '@/renderer/styles';
|
||||
import { LibraryItem } from '@/renderer/types';
|
||||
import {
|
||||
ViewType,
|
||||
@@ -36,20 +39,20 @@ import {
|
||||
} from '../../library/components/ViewTypeButton';
|
||||
|
||||
const FILTERS = [
|
||||
{ name: 'Title', value: AlbumSort.NAME },
|
||||
{ name: 'Date added', value: AlbumSort.DATE_ADDED },
|
||||
{
|
||||
name: 'Date added (remote)',
|
||||
name: 'Date Added (remote)',
|
||||
value: AlbumSort.DATE_ADDED_REMOTE,
|
||||
},
|
||||
{ name: 'Date released', 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: 'Release Date', value: AlbumSort.DATE_RELEASED },
|
||||
{ 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: 'Descending', value: SortOrder.DESC },
|
||||
];
|
||||
@@ -66,15 +69,20 @@ export const AlbumListRoute = () => {
|
||||
sortBy: AlbumSort.NAME,
|
||||
});
|
||||
|
||||
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilterGroup>({
|
||||
const [isAdvFilter, toggleAdvFilter] = useToggle();
|
||||
const [rawAdvFilters, setRawAdvFilters] = useState<AdvancedFilterGroup>({
|
||||
group: [],
|
||||
rules: [{ field: null, operator: null, uniqueId: nanoid(), value: null }],
|
||||
type: FilterGroupType.AND,
|
||||
uniqueId: nanoid(),
|
||||
});
|
||||
|
||||
const [debouncedFilters] = useDebouncedValue(advancedFilters, 300);
|
||||
const encoded = encodeURI(JSON.stringify(debouncedFilters));
|
||||
const [debouncedAdvFilters] = useDebouncedValue(rawAdvFilters, 300);
|
||||
|
||||
const advancedFilters = useMemo(() => {
|
||||
const value = formatAdvancedFiltersQuery(debouncedAdvFilters);
|
||||
return encodeURI(JSON.stringify(value));
|
||||
}, [debouncedAdvFilters]);
|
||||
|
||||
const serverFolders = useMemo(() => {
|
||||
const server = servers?.data.find((server) => server.id === serverId);
|
||||
@@ -82,7 +90,7 @@ export const AlbumListRoute = () => {
|
||||
}, [serverId, servers]);
|
||||
|
||||
const { data: albums } = useAlbumList({
|
||||
advancedFilters: encoded,
|
||||
advancedFilters,
|
||||
orderBy: filters.orderBy,
|
||||
serverFolderId: filters.serverFolderId,
|
||||
skip: 0,
|
||||
@@ -97,12 +105,12 @@ export const AlbumListRoute = () => {
|
||||
skip,
|
||||
take,
|
||||
...filters,
|
||||
advancedFilters: encoded,
|
||||
advancedFilters,
|
||||
}),
|
||||
async () =>
|
||||
api.albums.getAlbumList(
|
||||
{ serverId },
|
||||
{ skip, take, ...filters, advancedFilters: encoded }
|
||||
{ skip, take, ...filters, advancedFilters }
|
||||
)
|
||||
);
|
||||
|
||||
@@ -120,7 +128,14 @@ export const AlbumListRoute = () => {
|
||||
|
||||
return albums;
|
||||
},
|
||||
[encoded, filters, isImageTokenRequired, queryClient, serverId, serverToken]
|
||||
[
|
||||
advancedFilters,
|
||||
filters,
|
||||
isImageTokenRequired,
|
||||
queryClient,
|
||||
serverId,
|
||||
serverToken,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -128,7 +143,7 @@ export const AlbumListRoute = () => {
|
||||
<VirtualGridContainer>
|
||||
<Group m={10} position="apart">
|
||||
<Group>
|
||||
<Text font={Font.POPPINS} size="lg">
|
||||
<Text noSelect size="lg">
|
||||
Albums
|
||||
</Text>
|
||||
<DropdownMenu position="bottom-start">
|
||||
@@ -144,26 +159,54 @@ export const AlbumListRoute = () => {
|
||||
{FILTERS.map((filter) => (
|
||||
<DropdownMenu.Item
|
||||
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 })}
|
||||
>
|
||||
{filter.name}
|
||||
</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>
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
<Button compact variant="subtle">
|
||||
<Group>
|
||||
{SORT.find((s) => s.value === filters.orderBy)?.name}{' '}
|
||||
{ORDER.find((s) => s.value === filters.orderBy)?.name}{' '}
|
||||
<RiArrowDownSLine size={15} />
|
||||
</Group>
|
||||
</Button>
|
||||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
{SORT.map((sort) => (
|
||||
{ORDER.map((sort) => (
|
||||
<DropdownMenu.Item
|
||||
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 })}
|
||||
>
|
||||
{sort.name}
|
||||
@@ -204,10 +247,37 @@ export const AlbumListRoute = () => {
|
||||
/>
|
||||
</Group>
|
||||
</Group>
|
||||
<AdvancedFilters
|
||||
filters={advancedFilters}
|
||||
setFilters={setAdvancedFilters}
|
||||
/>
|
||||
{isAdvFilter && (
|
||||
<>
|
||||
<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>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
|
||||
Reference in New Issue
Block a user