Persist album list config

This commit is contained in:
jeffvli
2022-11-20 01:53:20 -08:00
parent b51a79c3cd
commit 6f0c523559
5 changed files with 442 additions and 262 deletions
+9 -20
View File
@@ -1,30 +1,11 @@
import { ax } from '@/renderer/lib/axios'; import { ax } from '@/renderer/lib/axios';
import { SortOrder } from '@/renderer/types'; import { AlbumSort, SortOrder } from '@/renderer/types';
import { import {
AlbumDetailResponse, AlbumDetailResponse,
AlbumListResponse, AlbumListResponse,
PaginationParams, PaginationParams,
} from './types'; } from './types';
export enum AlbumSort {
DATE_ADDED = 'added',
DATE_ADDED_REMOTE = 'addedRemote',
DATE_RELEASED = 'released',
DATE_RELEASED_YEAR = 'year',
FAVORITE = 'favorite',
NAME = 'name',
RANDOM = 'random',
RATING = 'rating',
}
export type AlbumListParams = PaginationParams & {
advancedFilters?: string;
orderBy: SortOrder;
serverFolderId?: string[];
serverUrlId?: string;
sortBy: AlbumSort;
};
const getAlbumDetail = async ( const getAlbumDetail = async (
query: { albumId: string; serverId: string }, query: { albumId: string; serverId: string },
signal?: AbortSignal signal?: AbortSignal
@@ -42,6 +23,14 @@ const getAlbumDetail = async (
return data; return data;
}; };
export type AlbumListParams = PaginationParams & {
advancedFilters?: string;
orderBy: SortOrder;
serverFolderId?: string[];
serverUrlId?: string;
sortBy: AlbumSort;
};
const getAlbumList = async ( const getAlbumList = async (
query: { serverId: string }, query: { serverId: string },
params: AlbumListParams, params: AlbumListParams,
@@ -1,4 +1,4 @@
import { useMemo } from 'react'; import { forwardRef, Ref, useImperativeHandle, useMemo, useState } from 'react';
import { Stack, Group } from '@mantine/core'; import { Stack, Group } from '@mantine/core';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
@@ -15,25 +15,11 @@ import {
TextInput, TextInput,
} from '@/renderer/components'; } from '@/renderer/components';
import { useGenreList } from '@/renderer/features/genres'; import { useGenreList } from '@/renderer/features/genres';
import {
export enum FilterGroupType { AdvancedFilterGroup,
AND = 'AND', AdvancedFilterRule,
OR = 'OR', FilterGroupType,
} } from '@/renderer/types';
export type AdvancedFilterRule = {
field?: string | null;
operator?: string | null;
uniqueId: string;
value?: string | number | Date | undefined | null | any;
};
export type AdvancedFilterGroup = {
group: AdvancedFilterGroup[];
rules: AdvancedFilterRule[];
type: FilterGroupType;
uniqueId: string;
};
const DATE_FILTER_OPTIONS_DATA = [ const DATE_FILTER_OPTIONS_DATA = [
{ label: 'is before', value: '<' }, { label: 'is before', value: '<' },
@@ -787,227 +773,274 @@ const FilterGroup = ({
); );
}; };
export const AdvancedFilters = ({ filters, setFilters }: any) => { const DEFAULT_ADVANCED_FILTERS = {
const handleAddRuleGroup = (args: AddArgs) => { group: [],
const { level, groupIndex } = args; rules: [
const filtersCopy = { ...filters }; {
field: '',
operator: '',
uniqueId: nanoid(),
value: '',
},
],
type: FilterGroupType.AND,
uniqueId: nanoid(),
};
const getPath = (level: number) => { interface AdvancedFiltersProps {
if (level === 0) return 'group'; defaultFilters?: AdvancedFilterGroup;
onChange: (filters: AdvancedFilterGroup) => void;
}
export interface AdvancedFiltersRef {
reset: () => void;
}
export const AdvancedFilters = forwardRef(
(
{ defaultFilters, onChange }: AdvancedFiltersProps,
ref: Ref<AdvancedFiltersRef>
) => {
const [filters, setFilters] = useState<AdvancedFilterGroup>(
defaultFilters || DEFAULT_ADVANCED_FILTERS
);
useImperativeHandle(ref, () => ({
reset() {
setFilters(DEFAULT_ADVANCED_FILTERS);
},
}));
const setFilterHandler = (newFilters: AdvancedFilterGroup) => {
setFilters(newFilters);
onChange(newFilters);
};
const handleAddRuleGroup = (args: AddArgs) => {
const { level, groupIndex } = args;
const filtersCopy = { ...filters };
const getPath = (level: number) => {
if (level === 0) return 'group';
const str = [];
for (const index of groupIndex) {
str.push(`group[${index}]`);
}
return `${str.join('.')}.group`;
};
const path = getPath(level);
const updatedFilters = set(filtersCopy, path, [
...get(filtersCopy, path),
{
group: [],
rules: [
{
field: '',
operator: '',
uniqueId: nanoid(),
value: '',
},
],
type: FilterGroupType.AND,
uniqueId: nanoid(),
},
]);
setFilterHandler(updatedFilters);
};
const handleDeleteRuleGroup = (args: DeleteArgs) => {
const { uniqueId, level, groupIndex } = args;
const filtersCopy = { ...filters };
const getPath = (level: number) => {
if (level === 0) return 'group';
const str = [];
for (let i = 0; i < groupIndex.length; i += 1) {
if (i !== groupIndex.length - 1) {
str.push(`group[${groupIndex[i]}]`);
} else {
str.push(`group`);
}
}
return `${str.join('.')}`;
};
const path = getPath(level);
const updatedFilters = set(filtersCopy, path, [
...get(filtersCopy, path).filter(
(group: AdvancedFilterGroup) => group.uniqueId !== uniqueId
),
]);
setFilterHandler(updatedFilters);
};
const getRulePath = (level: number, groupIndex: number[]) => {
if (level === 0) return 'rules';
const str = []; const str = [];
for (const index of groupIndex) { for (const index of groupIndex) {
str.push(`group[${index}]`); str.push(`group[${index}]`);
} }
return `${str.join('.')}.group`; return `${str.join('.')}.rules`;
}; };
const path = getPath(level); const handleAddRule = (args: AddArgs) => {
const updatedFilters = set(filtersCopy, path, [ const { level, groupIndex } = args;
...get(filtersCopy, path), const filtersCopy = { ...filters };
{
group: [], const path = getRulePath(level, groupIndex);
rules: [ const updatedFilters = set(filtersCopy, path, [
{ ...get(filtersCopy, path),
field: '', {
operator: '', field: null,
uniqueId: nanoid(), operator: null,
uniqueId: nanoid(),
value: null,
},
]);
setFilterHandler(updatedFilters);
};
const handleDeleteRule = (args: DeleteArgs) => {
const { uniqueId, level, groupIndex } = args;
const filtersCopy = { ...filters };
const path = getRulePath(level, groupIndex);
const updatedFilters = set(
filtersCopy,
path,
get(filtersCopy, path).filter(
(rule: AdvancedFilterRule) => rule.uniqueId !== uniqueId
)
);
setFilterHandler(updatedFilters);
};
const handleChangeField = (args: any) => {
const { uniqueId, level, groupIndex, value } = args;
const filtersCopy = { ...filters };
const path = getRulePath(level, groupIndex);
const updatedFilters = set(
filtersCopy,
path,
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
if (rule.uniqueId !== uniqueId) return rule;
const defaultOperator = FILTER_OPTIONS_DATA.find(
(option) => option.value === value
)?.default;
return {
...rule,
field: value,
operator: defaultOperator || '',
value: '', value: '',
}, };
], })
type: FilterGroupType.AND, );
uniqueId: nanoid(),
},
]);
setFilters(updatedFilters); setFilterHandler(updatedFilters);
}; };
const handleDeleteRuleGroup = (args: DeleteArgs) => { const handleChangeType = (args: any) => {
const { uniqueId, level, groupIndex } = args; const { level, groupIndex, value } = args;
const filtersCopy = { ...filters };
const getPath = (level: number) => { const filtersCopy = { ...filters };
if (level === 0) return 'group';
const str = []; if (level === 0) {
for (let i = 0; i < groupIndex.length; i += 1) { return setFilterHandler({ ...filtersCopy, type: value });
if (i !== groupIndex.length - 1) { }
const getTypePath = () => {
const str = [];
for (let i = 0; i < groupIndex.length; i += 1) {
str.push(`group[${groupIndex[i]}]`); str.push(`group[${groupIndex[i]}]`);
} else {
str.push(`group`);
} }
}
return `${str.join('.')}`; return `${str.join('.')}`;
};
const path = getTypePath();
const updatedFilters = set(filtersCopy, path, {
...get(filtersCopy, path),
type: value,
});
return setFilterHandler(updatedFilters);
}; };
const path = getPath(level); const handleChangeOperator = (args: any) => {
const { uniqueId, level, groupIndex, value } = args;
const filtersCopy = { ...filters };
const updatedFilters = set(filtersCopy, path, [ const path = getRulePath(level, groupIndex);
...get(filtersCopy, path).filter( const updatedFilters = set(
(group: AdvancedFilterGroup) => group.uniqueId !== uniqueId filtersCopy,
), path,
]); get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
if (rule.uniqueId !== uniqueId) return rule;
return {
...rule,
operator: value,
};
})
);
setFilters(updatedFilters); setFilterHandler(updatedFilters);
};
const getRulePath = (level: number, groupIndex: number[]) => {
if (level === 0) return 'rules';
const str = [];
for (const index of groupIndex) {
str.push(`group[${index}]`);
}
return `${str.join('.')}.rules`;
};
const handleAddRule = (args: AddArgs) => {
const { level, groupIndex } = args;
const filtersCopy = { ...filters };
const path = getRulePath(level, groupIndex);
const updatedFilters = set(filtersCopy, path, [
...get(filtersCopy, path),
{
field: null,
operator: null,
uniqueId: nanoid(),
value: null,
},
]);
setFilters(updatedFilters);
};
const handleDeleteRule = (args: DeleteArgs) => {
const { uniqueId, level, groupIndex } = args;
const filtersCopy = { ...filters };
const path = getRulePath(level, groupIndex);
const updatedFilters = set(
filtersCopy,
path,
get(filtersCopy, path).filter(
(rule: AdvancedFilterRule) => rule.uniqueId !== uniqueId
)
);
setFilters(updatedFilters);
};
const handleChangeField = (args: any) => {
const { uniqueId, level, groupIndex, value } = args;
const filtersCopy = { ...filters };
const path = getRulePath(level, groupIndex);
const updatedFilters = set(
filtersCopy,
path,
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
if (rule.uniqueId !== uniqueId) return rule;
const defaultOperator = FILTER_OPTIONS_DATA.find(
(option) => option.value === value
)?.default;
return {
...rule,
field: value,
operator: defaultOperator || '',
value: '',
};
})
);
setFilters(updatedFilters);
};
const handleChangeType = (args: any) => {
const { level, groupIndex, value } = args;
const filtersCopy = { ...filters };
if (level === 0) {
return setFilters({ ...filtersCopy, type: value });
}
const getTypePath = () => {
const str = [];
for (let i = 0; i < groupIndex.length; i += 1) {
str.push(`group[${groupIndex[i]}]`);
}
return `${str.join('.')}`;
}; };
const path = getTypePath(); const handleChangeValue = (args: any) => {
const updatedFilters = set(filtersCopy, path, { const { uniqueId, level, groupIndex, value } = args;
...get(filtersCopy, path), const filtersCopy = { ...filters };
type: value,
});
return setFilters(updatedFilters); const path = getRulePath(level, groupIndex);
}; const updatedFilters = set(
filtersCopy,
path,
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
if (rule.uniqueId !== uniqueId) return rule;
return {
...rule,
value,
};
})
);
const handleChangeOperator = (args: any) => { setFilterHandler(updatedFilters);
const { uniqueId, level, groupIndex, value } = args; };
const filtersCopy = { ...filters };
const path = getRulePath(level, groupIndex); return (
const updatedFilters = set( <>
filtersCopy, <FilterGroup
path, data={filters}
get(filtersCopy, path).map((rule: AdvancedFilterRule) => { groupIndex={[]}
if (rule.uniqueId !== uniqueId) return rule; level={0}
return { uniqueId={filters.uniqueId}
...rule, onAddRule={handleAddRule}
operator: value, onAddRuleGroup={handleAddRuleGroup}
}; onChangeField={handleChangeField}
}) onChangeOperator={handleChangeOperator}
onChangeType={handleChangeType}
onChangeValue={handleChangeValue}
onDeleteRule={handleDeleteRule}
onDeleteRuleGroup={handleDeleteRuleGroup}
/>
</>
); );
}
);
setFilters(updatedFilters); AdvancedFilters.defaultProps = {
}; defaultFilters: undefined,
const handleChangeValue = (args: any) => {
const { uniqueId, level, groupIndex, value } = args;
const filtersCopy = { ...filters };
const path = getRulePath(level, groupIndex);
const updatedFilters = set(
filtersCopy,
path,
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
if (rule.uniqueId !== uniqueId) return rule;
return {
...rule,
value,
};
})
);
setFilters(updatedFilters);
};
return (
<>
<FilterGroup
data={filters}
groupIndex={[]}
level={0}
uniqueId={filters.uniqueId}
onAddRule={handleAddRule}
onAddRuleGroup={handleAddRuleGroup}
onChangeField={handleChangeField}
onChangeOperator={handleChangeOperator}
onChangeType={handleChangeType}
onChangeValue={handleChangeValue}
onDeleteRule={handleDeleteRule}
onDeleteRuleGroup={handleDeleteRuleGroup}
/>
</>
);
}; };
@@ -1,9 +1,10 @@
/* eslint-disable no-plusplus */ /* eslint-disable no-plusplus */
import { useState, useCallback, useMemo, MouseEvent } from 'react'; import { useCallback, useMemo, MouseEvent, useRef } from 'react';
import { Group, Box } from '@mantine/core'; import { Group, Box } from '@mantine/core';
import { useDebouncedValue, useSetState, useToggle } from '@mantine/hooks'; import { useDebouncedValue } from '@mantine/hooks';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { import {
@@ -12,8 +13,8 @@ import {
RiSettings2Fill, RiSettings2Fill,
} from 'react-icons/ri'; } from 'react-icons/ri';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { ListOnScrollProps } from 'react-window';
import { api } from '@/renderer/api'; import { api } from '@/renderer/api';
import { AlbumSort } from '@/renderer/api/albums.api';
import { queryKeys } from '@/renderer/api/query-keys'; import { queryKeys } from '@/renderer/api/query-keys';
import { SortOrder } from '@/renderer/api/types'; import { SortOrder } from '@/renderer/api/types';
import { import {
@@ -29,17 +30,22 @@ import {
VirtualInfiniteGrid, VirtualInfiniteGrid,
} from '@/renderer/components'; } from '@/renderer/components';
import { import {
AdvancedFilterGroup,
AdvancedFilters, AdvancedFilters,
FilterGroupType,
encodeAdvancedFiltersQuery, encodeAdvancedFiltersQuery,
AdvancedFiltersRef,
} from '@/renderer/features/albums/components/advanced-filters'; } from '@/renderer/features/albums/components/advanced-filters';
import { useAlbumList } from '@/renderer/features/albums/queries/get-album-list'; import { useAlbumList } from '@/renderer/features/albums/queries/get-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 { useAppStore, useAuthStore } from '@/renderer/store'; import { useAppStore, useAuthStore } from '@/renderer/store';
import { LibraryItem, CardDisplayType } from '@/renderer/types'; import {
LibraryItem,
CardDisplayType,
AlbumSort,
FilterGroupType,
AdvancedFilterGroup,
} from '@/renderer/types';
const FILTERS = [ const FILTERS = [
{ name: 'Title', value: AlbumSort.NAME }, { name: 'Title', value: AlbumSort.NAME },
@@ -81,19 +87,16 @@ export const AlbumListRoute = () => {
const setPage = useAppStore((state) => state.setPage); const setPage = useAppStore((state) => state.setPage);
const serverId = useAuthStore((state) => state.currentServer?.id) || ''; const serverId = useAuthStore((state) => state.currentServer?.id) || '';
const serverListQuery = useServerList({ enabled: true }); const serverListQuery = useServerList({ enabled: true });
const [filters, setFilters] = useSetState({ const filters = page.list.filter;
orderBy: SortOrder.ASC, const advancedFiltersRef = useRef<AdvancedFiltersRef>(null);
serverFolderId: [] as string[],
sortBy: AlbumSort.NAME,
});
const [isAdvFilter, toggleAdvFilter] = useToggle(); const isAdvFilter = page.list.advancedFilter.enabled;
const [rawAdvFilters, setRawAdvFilters] = useState<AdvancedFilterGroup>(
DEFAULT_ADVANCED_FILTERS const [debouncedAdvFilters] = useDebouncedValue(
page.list.advancedFilter.filter,
500
); );
const [debouncedAdvFilters] = useDebouncedValue(rawAdvFilters, 500);
const advancedFilters = useMemo(() => { const advancedFilters = useMemo(() => {
if (!isAdvFilter) { if (!isAdvFilter) {
return encodeAdvancedFiltersQuery(DEFAULT_ADVANCED_FILTERS); return encodeAdvancedFiltersQuery(DEFAULT_ADVANCED_FILTERS);
@@ -103,7 +106,17 @@ export const AlbumListRoute = () => {
}, [debouncedAdvFilters, isAdvFilter]); }, [debouncedAdvFilters, isAdvFilter]);
const handleResetAdvancedFilters = () => { const handleResetAdvancedFilters = () => {
setRawAdvFilters(DEFAULT_ADVANCED_FILTERS); setPage('albums', {
...page,
list: {
...page.list,
advancedFilter: {
...page.list.advancedFilter,
filter: DEFAULT_ADVANCED_FILTERS,
},
},
});
advancedFiltersRef.current?.reset();
}; };
const serverFolders = useMemo(() => { const serverFolders = useMemo(() => {
@@ -173,32 +186,84 @@ export const AlbumListRoute = () => {
const handleSetFilter = (e: MouseEvent<HTMLButtonElement>) => { const handleSetFilter = (e: MouseEvent<HTMLButtonElement>) => {
if (!e.currentTarget?.value) return; if (!e.currentTarget?.value) return;
setFilters({ sortBy: e.currentTarget.value as AlbumSort }); setPage('albums', {
list: {
...page.list,
filter: {
...page.list.filter,
sortBy: e.currentTarget.value as AlbumSort,
},
},
});
}; };
const handleSetOrder = (e: MouseEvent<HTMLButtonElement>) => { const handleSetOrder = (e: MouseEvent<HTMLButtonElement>) => {
if (!e.currentTarget?.value) return; if (!e.currentTarget?.value) return;
setFilters({ orderBy: e.currentTarget.value as SortOrder }); setPage('albums', {
list: {
...page.list,
filter: {
...page.list.filter,
orderBy: e.currentTarget.value as SortOrder,
},
},
});
}; };
const handleSetServerFolder = (e: MouseEvent<HTMLButtonElement>) => { const handleSetServerFolder = (e: MouseEvent<HTMLButtonElement>) => {
if (!e.currentTarget?.value) return; if (!e.currentTarget?.value) return;
const value = e.currentTarget.value as string; const value = e.currentTarget.value as string;
if (filters.serverFolderId.includes(value)) { if (filters.serverFolderId.includes(value)) {
setFilters({ setPage('albums', {
serverFolderId: filters.serverFolderId.filter((id) => id !== value), list: {
...page.list,
filter: {
...page.list.filter,
serverFolderId: filters.serverFolderId.filter((id) => id !== value),
},
},
}); });
} else { } else {
setFilters({ setPage('albums', {
serverFolderId: [...filters.serverFolderId, value], list: {
...page.list,
filter: {
...page.list.filter,
serverFolderId: [...filters.serverFolderId, value],
},
},
}); });
} }
}; };
const handleToggleAdvancedFilters = () => { const handleToggleAdvancedFilters = () => {
toggleAdvFilter(); const enabled = !page.list.advancedFilter.enabled;
setPage('albums', {
...page,
list: {
...page.list,
advancedFilter: {
...page.list.advancedFilter,
enabled,
...(!enabled && { filter: DEFAULT_ADVANCED_FILTERS }),
},
},
});
}; };
const handleUpdateAdvancedFilters = debounce((e: AdvancedFilterGroup) => {
setPage('albums', {
...page,
list: {
...page.list,
advancedFilter: {
...page.list.advancedFilter,
filter: e,
},
},
});
}, 150);
const handleSetViewType = (e: MouseEvent<HTMLButtonElement>) => { const handleSetViewType = (e: MouseEvent<HTMLButtonElement>) => {
if (!e.currentTarget?.value) return; if (!e.currentTarget?.value) return;
const type = e.currentTarget.value; const type = e.currentTarget.value;
@@ -231,6 +296,16 @@ export const AlbumListRoute = () => {
} }
}; };
const handleGridScroll = debounce((e: ListOnScrollProps) => {
setPage('albums', {
...page,
list: {
...page.list,
gridScrollOffset: e.scrollOffset,
},
});
}, 50);
return ( return (
<AnimatedPage> <AnimatedPage>
<VirtualGridContainer> <VirtualGridContainer>
@@ -418,8 +493,9 @@ export const AlbumListRoute = () => {
</Group> </Group>
<Box p={10}> <Box p={10}>
<AdvancedFilters <AdvancedFilters
filters={rawAdvFilters} ref={advancedFiltersRef}
setFilters={setRawAdvFilters} defaultFilters={page.list.advancedFilter.filter}
onChange={handleUpdateAdvancedFilters}
/> />
</Box> </Box>
</ScrollArea> </ScrollArea>
@@ -456,6 +532,7 @@ export const AlbumListRoute = () => {
display={page.list?.display || CardDisplayType.CARD} display={page.list?.display || CardDisplayType.CARD}
fetchFn={fetch} fetchFn={fetch}
height={height} height={height}
initialScrollOffset={page.list?.gridScrollOffset || 0}
itemCount={albumListQuery?.data?.pagination.totalEntries || 0} itemCount={albumListQuery?.data?.pagination.totalEntries || 0}
itemGap={20} itemGap={20}
itemSize={150 + page.list?.size} itemSize={150 + page.list?.size}
@@ -467,6 +544,7 @@ export const AlbumListRoute = () => {
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }], slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
}} }}
width={width} width={width}
onScroll={handleGridScroll}
/> />
)} )}
</AutoSizer> </AutoSizer>
+51 -1
View File
@@ -1,8 +1,16 @@
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import { nanoid } from 'nanoid/non-secure';
import create from 'zustand'; import create from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { CardDisplayType, Platform } from '@/renderer/types'; import {
AdvancedFilterGroup,
AlbumSort,
CardDisplayType,
FilterGroupType,
Platform,
SortOrder,
} from '@/renderer/types';
type SidebarProps = { type SidebarProps = {
expanded: string[]; expanded: string[];
@@ -16,8 +24,24 @@ type LibraryPageProps = {
list: ListProps; list: ListProps;
}; };
type ListFilter = {
orderBy: SortOrder;
search?: string;
serverFolderId: string[];
sortBy: AlbumSort;
};
type ListAdvancedFilter = {
enabled: boolean;
filter: AdvancedFilterGroup;
};
type ListProps = { type ListProps = {
advancedFilter: ListAdvancedFilter;
display: CardDisplayType; display: CardDisplayType;
filter: ListFilter;
gridScrollOffset: number;
listScrollOffset: number;
size: number; size: number;
type: 'list' | 'grid'; type: 'list' | 'grid';
}; };
@@ -35,6 +59,20 @@ export interface AppState {
}; };
} }
const DEFAULT_ADVANCED_FILTERS = {
group: [],
rules: [
{
field: '',
operator: '',
uniqueId: nanoid(),
value: '',
},
],
type: FilterGroupType.AND,
uniqueId: nanoid(),
};
export interface AppSlice extends AppState { export interface AppSlice extends AppState {
setAppStore: (data: Partial<AppSlice>) => void; setAppStore: (data: Partial<AppSlice>) => void;
setPage: (page: 'albums', options: Partial<LibraryPageProps>) => void; setPage: (page: 'albums', options: Partial<LibraryPageProps>) => void;
@@ -47,7 +85,19 @@ export const useAppStore = create<AppSlice>()(
immer((set, get) => ({ immer((set, get) => ({
albums: { albums: {
list: { list: {
advancedFilter: {
enabled: false,
filter: DEFAULT_ADVANCED_FILTERS,
},
display: CardDisplayType.CARD, display: CardDisplayType.CARD,
filter: {
orderBy: SortOrder.DESC,
search: '',
serverFolderId: [],
sortBy: AlbumSort.DATE_ADDED_REMOTE,
},
gridScrollOffset: 0,
listScrollOffset: 0,
size: 50, size: 50,
type: 'grid', type: 'grid',
}, },
+30
View File
@@ -91,11 +91,41 @@ export interface UniqueId {
uniqueId: string; uniqueId: string;
} }
export enum AlbumSort {
DATE_ADDED = 'added',
DATE_ADDED_REMOTE = 'addedRemote',
DATE_RELEASED = 'released',
DATE_RELEASED_YEAR = 'year',
FAVORITE = 'favorite',
NAME = 'name',
RANDOM = 'random',
RATING = 'rating',
}
export enum SortOrder { export enum SortOrder {
ASC = 'asc', ASC = 'asc',
DESC = 'desc', DESC = 'desc',
} }
export enum FilterGroupType {
AND = 'AND',
OR = 'OR',
}
export type AdvancedFilterRule = {
field?: string | null;
operator?: string | null;
uniqueId: string;
value?: string | number | Date | undefined | null | any;
};
export type AdvancedFilterGroup = {
group: AdvancedFilterGroup[];
rules: AdvancedFilterRule[];
type: FilterGroupType;
uniqueId: string;
};
export enum TableColumn { export enum TableColumn {
ALBUM = 'album', ALBUM = 'album',
ALBUM_ARTIST = 'albumArtist', ALBUM_ARTIST = 'albumArtist',