mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
Persist album list config
This commit is contained in:
@@ -1,30 +1,11 @@
|
||||
import { ax } from '@/renderer/lib/axios';
|
||||
import { SortOrder } from '@/renderer/types';
|
||||
import { AlbumSort, SortOrder } from '@/renderer/types';
|
||||
import {
|
||||
AlbumDetailResponse,
|
||||
AlbumListResponse,
|
||||
PaginationParams,
|
||||
} 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 (
|
||||
query: { albumId: string; serverId: string },
|
||||
signal?: AbortSignal
|
||||
@@ -42,6 +23,14 @@ const getAlbumDetail = async (
|
||||
return data;
|
||||
};
|
||||
|
||||
export type AlbumListParams = PaginationParams & {
|
||||
advancedFilters?: string;
|
||||
orderBy: SortOrder;
|
||||
serverFolderId?: string[];
|
||||
serverUrlId?: string;
|
||||
sortBy: AlbumSort;
|
||||
};
|
||||
|
||||
const getAlbumList = async (
|
||||
query: { serverId: string },
|
||||
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 dayjs from 'dayjs';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
@@ -15,25 +15,11 @@ import {
|
||||
TextInput,
|
||||
} from '@/renderer/components';
|
||||
import { useGenreList } from '@/renderer/features/genres';
|
||||
|
||||
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;
|
||||
};
|
||||
import {
|
||||
AdvancedFilterGroup,
|
||||
AdvancedFilterRule,
|
||||
FilterGroupType,
|
||||
} from '@/renderer/types';
|
||||
|
||||
const DATE_FILTER_OPTIONS_DATA = [
|
||||
{ label: 'is before', value: '<' },
|
||||
@@ -787,227 +773,274 @@ const FilterGroup = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const AdvancedFilters = ({ filters, setFilters }: any) => {
|
||||
const handleAddRuleGroup = (args: AddArgs) => {
|
||||
const { level, groupIndex } = args;
|
||||
const filtersCopy = { ...filters };
|
||||
const DEFAULT_ADVANCED_FILTERS = {
|
||||
group: [],
|
||||
rules: [
|
||||
{
|
||||
field: '',
|
||||
operator: '',
|
||||
uniqueId: nanoid(),
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
type: FilterGroupType.AND,
|
||||
uniqueId: nanoid(),
|
||||
};
|
||||
|
||||
const getPath = (level: number) => {
|
||||
if (level === 0) return 'group';
|
||||
interface AdvancedFiltersProps {
|
||||
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 = [];
|
||||
for (const index of groupIndex) {
|
||||
str.push(`group[${index}]`);
|
||||
}
|
||||
|
||||
return `${str.join('.')}.group`;
|
||||
return `${str.join('.')}.rules`;
|
||||
};
|
||||
|
||||
const path = getPath(level);
|
||||
const updatedFilters = set(filtersCopy, path, [
|
||||
...get(filtersCopy, path),
|
||||
{
|
||||
group: [],
|
||||
rules: [
|
||||
{
|
||||
field: '',
|
||||
operator: '',
|
||||
uniqueId: nanoid(),
|
||||
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,
|
||||
},
|
||||
]);
|
||||
|
||||
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: '',
|
||||
},
|
||||
],
|
||||
type: FilterGroupType.AND,
|
||||
uniqueId: nanoid(),
|
||||
},
|
||||
]);
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
setFilters(updatedFilters);
|
||||
};
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const handleDeleteRuleGroup = (args: DeleteArgs) => {
|
||||
const { uniqueId, level, groupIndex } = args;
|
||||
const filtersCopy = { ...filters };
|
||||
const handleChangeType = (args: any) => {
|
||||
const { level, groupIndex, value } = args;
|
||||
|
||||
const getPath = (level: number) => {
|
||||
if (level === 0) return 'group';
|
||||
const filtersCopy = { ...filters };
|
||||
|
||||
const str = [];
|
||||
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||
if (i !== groupIndex.length - 1) {
|
||||
if (level === 0) {
|
||||
return setFilterHandler({ ...filtersCopy, type: value });
|
||||
}
|
||||
|
||||
const getTypePath = () => {
|
||||
const str = [];
|
||||
for (let i = 0; i < groupIndex.length; i += 1) {
|
||||
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, [
|
||||
...get(filtersCopy, path).filter(
|
||||
(group: AdvancedFilterGroup) => group.uniqueId !== uniqueId
|
||||
),
|
||||
]);
|
||||
const path = getRulePath(level, groupIndex);
|
||||
const updatedFilters = set(
|
||||
filtersCopy,
|
||||
path,
|
||||
get(filtersCopy, path).map((rule: AdvancedFilterRule) => {
|
||||
if (rule.uniqueId !== uniqueId) return rule;
|
||||
return {
|
||||
...rule,
|
||||
operator: value,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
setFilters(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('.')}`;
|
||||
setFilterHandler(updatedFilters);
|
||||
};
|
||||
|
||||
const path = getTypePath();
|
||||
const updatedFilters = set(filtersCopy, path, {
|
||||
...get(filtersCopy, path),
|
||||
type: value,
|
||||
});
|
||||
const handleChangeValue = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const filtersCopy = { ...filters };
|
||||
|
||||
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) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const filtersCopy = { ...filters };
|
||||
setFilterHandler(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,
|
||||
operator: value,
|
||||
};
|
||||
})
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
setFilters(updatedFilters);
|
||||
};
|
||||
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
AdvancedFilters.defaultProps = {
|
||||
defaultFilters: undefined,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* 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 { useDebouncedValue, useSetState, useToggle } from '@mantine/hooks';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import debounce from 'lodash/debounce';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { nanoid } from 'nanoid';
|
||||
import {
|
||||
@@ -12,8 +13,8 @@ import {
|
||||
RiSettings2Fill,
|
||||
} from 'react-icons/ri';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { ListOnScrollProps } from 'react-window';
|
||||
import { api } from '@/renderer/api';
|
||||
import { AlbumSort } from '@/renderer/api/albums.api';
|
||||
import { queryKeys } from '@/renderer/api/query-keys';
|
||||
import { SortOrder } from '@/renderer/api/types';
|
||||
import {
|
||||
@@ -29,17 +30,22 @@ import {
|
||||
VirtualInfiniteGrid,
|
||||
} from '@/renderer/components';
|
||||
import {
|
||||
AdvancedFilterGroup,
|
||||
AdvancedFilters,
|
||||
FilterGroupType,
|
||||
encodeAdvancedFiltersQuery,
|
||||
AdvancedFiltersRef,
|
||||
} from '@/renderer/features/albums/components/advanced-filters';
|
||||
import { useAlbumList } from '@/renderer/features/albums/queries/get-album-list';
|
||||
import { useServerList } from '@/renderer/features/servers';
|
||||
import { AnimatedPage, useServerCredential } from '@/renderer/features/shared';
|
||||
import { AppRoute } from '@/renderer/router/routes';
|
||||
import { useAppStore, useAuthStore } from '@/renderer/store';
|
||||
import { LibraryItem, CardDisplayType } from '@/renderer/types';
|
||||
import {
|
||||
LibraryItem,
|
||||
CardDisplayType,
|
||||
AlbumSort,
|
||||
FilterGroupType,
|
||||
AdvancedFilterGroup,
|
||||
} from '@/renderer/types';
|
||||
|
||||
const FILTERS = [
|
||||
{ name: 'Title', value: AlbumSort.NAME },
|
||||
@@ -81,19 +87,16 @@ export const AlbumListRoute = () => {
|
||||
const setPage = useAppStore((state) => state.setPage);
|
||||
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
||||
const serverListQuery = useServerList({ enabled: true });
|
||||
const [filters, setFilters] = useSetState({
|
||||
orderBy: SortOrder.ASC,
|
||||
serverFolderId: [] as string[],
|
||||
sortBy: AlbumSort.NAME,
|
||||
});
|
||||
const filters = page.list.filter;
|
||||
const advancedFiltersRef = useRef<AdvancedFiltersRef>(null);
|
||||
|
||||
const [isAdvFilter, toggleAdvFilter] = useToggle();
|
||||
const [rawAdvFilters, setRawAdvFilters] = useState<AdvancedFilterGroup>(
|
||||
DEFAULT_ADVANCED_FILTERS
|
||||
const isAdvFilter = page.list.advancedFilter.enabled;
|
||||
|
||||
const [debouncedAdvFilters] = useDebouncedValue(
|
||||
page.list.advancedFilter.filter,
|
||||
500
|
||||
);
|
||||
|
||||
const [debouncedAdvFilters] = useDebouncedValue(rawAdvFilters, 500);
|
||||
|
||||
const advancedFilters = useMemo(() => {
|
||||
if (!isAdvFilter) {
|
||||
return encodeAdvancedFiltersQuery(DEFAULT_ADVANCED_FILTERS);
|
||||
@@ -103,7 +106,17 @@ export const AlbumListRoute = () => {
|
||||
}, [debouncedAdvFilters, isAdvFilter]);
|
||||
|
||||
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(() => {
|
||||
@@ -173,32 +186,84 @@ export const AlbumListRoute = () => {
|
||||
|
||||
const handleSetFilter = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
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>) => {
|
||||
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>) => {
|
||||
if (!e.currentTarget?.value) return;
|
||||
const value = e.currentTarget.value as string;
|
||||
if (filters.serverFolderId.includes(value)) {
|
||||
setFilters({
|
||||
serverFolderId: filters.serverFolderId.filter((id) => id !== value),
|
||||
setPage('albums', {
|
||||
list: {
|
||||
...page.list,
|
||||
filter: {
|
||||
...page.list.filter,
|
||||
serverFolderId: filters.serverFolderId.filter((id) => id !== value),
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setFilters({
|
||||
serverFolderId: [...filters.serverFolderId, value],
|
||||
setPage('albums', {
|
||||
list: {
|
||||
...page.list,
|
||||
filter: {
|
||||
...page.list.filter,
|
||||
serverFolderId: [...filters.serverFolderId, value],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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>) => {
|
||||
if (!e.currentTarget?.value) return;
|
||||
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 (
|
||||
<AnimatedPage>
|
||||
<VirtualGridContainer>
|
||||
@@ -418,8 +493,9 @@ export const AlbumListRoute = () => {
|
||||
</Group>
|
||||
<Box p={10}>
|
||||
<AdvancedFilters
|
||||
filters={rawAdvFilters}
|
||||
setFilters={setRawAdvFilters}
|
||||
ref={advancedFiltersRef}
|
||||
defaultFilters={page.list.advancedFilter.filter}
|
||||
onChange={handleUpdateAdvancedFilters}
|
||||
/>
|
||||
</Box>
|
||||
</ScrollArea>
|
||||
@@ -456,6 +532,7 @@ export const AlbumListRoute = () => {
|
||||
display={page.list?.display || CardDisplayType.CARD}
|
||||
fetchFn={fetch}
|
||||
height={height}
|
||||
initialScrollOffset={page.list?.gridScrollOffset || 0}
|
||||
itemCount={albumListQuery?.data?.pagination.totalEntries || 0}
|
||||
itemGap={20}
|
||||
itemSize={150 + page.list?.size}
|
||||
@@ -467,6 +544,7 @@ export const AlbumListRoute = () => {
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
||||
}}
|
||||
width={width}
|
||||
onScroll={handleGridScroll}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import merge from 'lodash/merge';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import create from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { CardDisplayType, Platform } from '@/renderer/types';
|
||||
import {
|
||||
AdvancedFilterGroup,
|
||||
AlbumSort,
|
||||
CardDisplayType,
|
||||
FilterGroupType,
|
||||
Platform,
|
||||
SortOrder,
|
||||
} from '@/renderer/types';
|
||||
|
||||
type SidebarProps = {
|
||||
expanded: string[];
|
||||
@@ -16,8 +24,24 @@ type LibraryPageProps = {
|
||||
list: ListProps;
|
||||
};
|
||||
|
||||
type ListFilter = {
|
||||
orderBy: SortOrder;
|
||||
search?: string;
|
||||
serverFolderId: string[];
|
||||
sortBy: AlbumSort;
|
||||
};
|
||||
|
||||
type ListAdvancedFilter = {
|
||||
enabled: boolean;
|
||||
filter: AdvancedFilterGroup;
|
||||
};
|
||||
|
||||
type ListProps = {
|
||||
advancedFilter: ListAdvancedFilter;
|
||||
display: CardDisplayType;
|
||||
filter: ListFilter;
|
||||
gridScrollOffset: number;
|
||||
listScrollOffset: number;
|
||||
size: number;
|
||||
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 {
|
||||
setAppStore: (data: Partial<AppSlice>) => void;
|
||||
setPage: (page: 'albums', options: Partial<LibraryPageProps>) => void;
|
||||
@@ -47,7 +85,19 @@ export const useAppStore = create<AppSlice>()(
|
||||
immer((set, get) => ({
|
||||
albums: {
|
||||
list: {
|
||||
advancedFilter: {
|
||||
enabled: false,
|
||||
filter: DEFAULT_ADVANCED_FILTERS,
|
||||
},
|
||||
display: CardDisplayType.CARD,
|
||||
filter: {
|
||||
orderBy: SortOrder.DESC,
|
||||
search: '',
|
||||
serverFolderId: [],
|
||||
sortBy: AlbumSort.DATE_ADDED_REMOTE,
|
||||
},
|
||||
gridScrollOffset: 0,
|
||||
listScrollOffset: 0,
|
||||
size: 50,
|
||||
type: 'grid',
|
||||
},
|
||||
|
||||
@@ -91,11 +91,41 @@ export interface UniqueId {
|
||||
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 {
|
||||
ASC = 'asc',
|
||||
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 {
|
||||
ALBUM = 'album',
|
||||
ALBUM_ARTIST = 'albumArtist',
|
||||
|
||||
Reference in New Issue
Block a user