Update song list implementation

This commit is contained in:
jeffvli 2023-07-19 20:04:56 -07:00
parent 85964bfded
commit 6cd27c3e88
8 changed files with 131 additions and 245 deletions

View File

@ -1,26 +1,27 @@
import { ChangeEvent, useMemo } from 'react';
import { Divider, Group, Stack } from '@mantine/core'; import { Divider, Group, Stack } from '@mantine/core';
import { MultiSelect, NumberInput, Switch, Text } from '/@/renderer/components';
import { SongListFilter, useListStoreActions, useSongListFilter } from '/@/renderer/store';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useGenreList } from '/@/renderer/features/genres'; import { ChangeEvent, useMemo } from 'react';
import { useListFilterByKey } from '../../../store/list.store';
import { LibraryItem } from '/@/renderer/api/types'; import { LibraryItem } from '/@/renderer/api/types';
import { MultiSelect, NumberInput, Switch, Text } from '/@/renderer/components';
import { useGenreList } from '/@/renderer/features/genres';
import { SongListFilter, useListStoreActions } from '/@/renderer/store';
interface JellyfinSongFiltersProps { interface JellyfinSongFiltersProps {
handleFilterChange: (filters: SongListFilter) => void; customFilters?: Partial<SongListFilter>;
id?: string; onFilterChange: (filters: SongListFilter) => void;
pageKey: string; pageKey: string;
serverId?: string; serverId?: string;
} }
export const JellyfinSongFilters = ({ export const JellyfinSongFilters = ({
id, customFilters,
pageKey, pageKey,
handleFilterChange, onFilterChange,
serverId, serverId,
}: JellyfinSongFiltersProps) => { }: JellyfinSongFiltersProps) => {
const { setFilter } = useListStoreActions(); const { setFilter } = useListStoreActions();
const filter = useSongListFilter({ id, key: pageKey }); const { filter } = useListFilterByKey({ key: pageKey });
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library // TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
const genreListQuery = useGenreList({ query: null, serverId }); const genreListQuery = useGenreList({ query: null, serverId });
@ -42,6 +43,7 @@ export const JellyfinSongFilters = ({
label: 'Is favorited', label: 'Is favorited',
onChange: (e: ChangeEvent<HTMLInputElement>) => { onChange: (e: ChangeEvent<HTMLInputElement>) => {
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { data: {
_custom: { _custom: {
...filter._custom, ...filter._custom,
@ -55,7 +57,7 @@ export const JellyfinSongFilters = ({
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters); onFilterChange(updatedFilters);
}, },
value: filter._custom?.jellyfin?.IsFavorite, value: filter._custom?.jellyfin?.IsFavorite,
}, },
@ -64,6 +66,7 @@ export const JellyfinSongFilters = ({
const handleMinYearFilter = debounce((e: number | string) => { const handleMinYearFilter = debounce((e: number | string) => {
if (typeof e === 'number' && (e < 1700 || e > 2300)) return; if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { data: {
_custom: { _custom: {
...filter._custom, ...filter._custom,
@ -77,12 +80,13 @@ export const JellyfinSongFilters = ({
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters); onFilterChange(updatedFilters);
}, 500); }, 500);
const handleMaxYearFilter = debounce((e: number | string) => { const handleMaxYearFilter = debounce((e: number | string) => {
if (typeof e === 'number' && (e < 1700 || e > 2300)) return; if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { data: {
_custom: { _custom: {
...filter._custom, ...filter._custom,
@ -96,12 +100,13 @@ export const JellyfinSongFilters = ({
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters); onFilterChange(updatedFilters);
}, 500); }, 500);
const handleGenresFilter = debounce((e: string[] | undefined) => { const handleGenresFilter = debounce((e: string[] | undefined) => {
const genreFilterString = e?.length ? e.join(',') : undefined; const genreFilterString = e?.length ? e.join(',') : undefined;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { data: {
_custom: { _custom: {
...filter._custom, ...filter._custom,
@ -115,7 +120,7 @@ export const JellyfinSongFilters = ({
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters); onFilterChange(updatedFilters);
}, 250); }, 250);
return ( return (

View File

@ -1,26 +1,26 @@
import { ChangeEvent, useMemo } from 'react';
import { Divider, Group, Stack } from '@mantine/core'; import { Divider, Group, Stack } from '@mantine/core';
import { NumberInput, Select, Switch, Text } from '/@/renderer/components';
import { SongListFilter, useListStoreActions, useSongListFilter } from '/@/renderer/store';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useGenreList } from '/@/renderer/features/genres'; import { ChangeEvent, useMemo } from 'react';
import { LibraryItem } from '/@/renderer/api/types'; import { LibraryItem } from '/@/renderer/api/types';
import { NumberInput, Select, Switch, Text } from '/@/renderer/components';
import { useGenreList } from '/@/renderer/features/genres';
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
interface NavidromeSongFiltersProps { interface NavidromeSongFiltersProps {
handleFilterChange: (filters: SongListFilter) => void; customFilters?: Partial<SongListFilter>;
id?: string; onFilterChange: (filters: SongListFilter) => void;
pageKey: string; pageKey: string;
serverId?: string; serverId?: string;
} }
export const NavidromeSongFilters = ({ export const NavidromeSongFilters = ({
handleFilterChange, customFilters,
onFilterChange,
pageKey, pageKey,
id,
serverId, serverId,
}: NavidromeSongFiltersProps) => { }: NavidromeSongFiltersProps) => {
const { setFilter } = useListStoreActions(); const { setFilter } = useListStoreActions();
const filter = useSongListFilter({ id, key: pageKey }); const filter = useListFilterByKey({ key: pageKey });
const genreListQuery = useGenreList({ query: null, serverId }); const genreListQuery = useGenreList({ query: null, serverId });
@ -34,6 +34,7 @@ export const NavidromeSongFilters = ({
const handleGenresFilter = debounce((e: string | null) => { const handleGenresFilter = debounce((e: string | null) => {
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { data: {
_custom: { _custom: {
...filter._custom, ...filter._custom,
@ -45,7 +46,8 @@ export const NavidromeSongFilters = ({
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters);
onFilterChange(updatedFilters);
}, 250); }, 250);
const toggleFilters = [ const toggleFilters = [
@ -53,6 +55,7 @@ export const NavidromeSongFilters = ({
label: 'Is favorited', label: 'Is favorited',
onChange: (e: ChangeEvent<HTMLInputElement>) => { onChange: (e: ChangeEvent<HTMLInputElement>) => {
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { data: {
_custom: { _custom: {
...filter._custom, ...filter._custom,
@ -65,7 +68,7 @@ export const NavidromeSongFilters = ({
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters); onFilterChange(updatedFilters);
}, },
value: filter._custom?.navidrome?.starred, value: filter._custom?.navidrome?.starred,
}, },
@ -73,6 +76,7 @@ export const NavidromeSongFilters = ({
const handleYearFilter = debounce((e: number | string) => { const handleYearFilter = debounce((e: number | string) => {
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { data: {
_custom: { _custom: {
...filter._custom, ...filter._custom,
@ -85,7 +89,7 @@ export const NavidromeSongFilters = ({
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters); onFilterChange(updatedFilters);
}, 500); }, 500);
return ( return (

View File

@ -1,8 +1,8 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { lazy, MutableRefObject, Suspense } from 'react'; import { lazy, MutableRefObject, Suspense } from 'react';
import { Spinner } from '/@/renderer/components'; import { Spinner } from '/@/renderer/components';
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context'; import { useListContext } from '/@/renderer/context/list-context';
import { useSongListStore } from '/@/renderer/store'; import { useListStoreByKey } from '/@/renderer/store';
import { ListDisplayType } from '/@/renderer/types'; import { ListDisplayType } from '/@/renderer/types';
const SongListTableView = lazy(() => const SongListTableView = lazy(() =>
@ -17,8 +17,8 @@ interface SongListContentProps {
} }
export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) => { export const SongListContent = ({ itemCount, tableRef }: SongListContentProps) => {
const { id, pageKey } = useSongListContext(); const { pageKey } = useListContext();
const { display } = useSongListStore({ id, key: pageKey }); const { display } = useListStoreByKey({ key: pageKey });
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER; const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER;

View File

@ -1,37 +1,31 @@
import { useCallback, useMemo, ChangeEvent, MutableRefObject, MouseEvent } from 'react';
import { IDatasource } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Divider, Flex, Group, Stack } from '@mantine/core'; import { Divider, Flex, Group, Stack } from '@mantine/core';
import { openModal } from '@mantine/modals'; import { openModal } from '@mantine/modals';
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
import { import {
RiFolder2Line,
RiMoreFill,
RiSettings3Fill,
RiPlayFill,
RiAddBoxFill, RiAddBoxFill,
RiAddCircleFill, RiAddCircleFill,
RiRefreshLine,
RiFilterFill, RiFilterFill,
RiFolder2Line,
RiMoreFill,
RiPlayFill,
RiRefreshLine,
RiSettings3Fill,
} from 'react-icons/ri'; } from 'react-icons/ri';
import { api } from '/@/renderer/api'; import { useListStoreByKey } from '../../../store/list.store';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { LibraryItem, SongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types'; import { LibraryItem, SongListSort, SortOrder } from '/@/renderer/api/types';
import { DropdownMenu, Button, Slider, MultiSelect, Switch, Text } from '/@/renderer/components'; import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components';
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
import { useListContext } from '/@/renderer/context/list-context';
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared'; import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters'; import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters';
import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters'; import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { useListFilterRefresh } from '/@/renderer/hooks/use-list-filter-refresh';
import { queryClient } from '/@/renderer/lib/react-query'; import { queryClient } from '/@/renderer/lib/react-query';
import { import { SongListFilter, useCurrentServer, useListStoreActions } from '/@/renderer/store';
SongListFilter, import { ListDisplayType, Play, ServerType, TableColumn } from '/@/renderer/types';
useCurrentServer,
useListStoreActions,
useSongListFilter,
useSongListStore,
} from '/@/renderer/store';
import { ListDisplayType, ServerType, Play, TableColumn } from '/@/renderer/types';
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context';
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
@ -83,10 +77,14 @@ interface SongListHeaderFiltersProps {
export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps) => { export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps) => {
const server = useCurrentServer(); const server = useCurrentServer();
const { id, pageKey, handlePlay } = useSongListContext(); const { pageKey, handlePlay, customFilters } = useListContext();
const { display, table } = useSongListStore({ id, key: pageKey }); const { display, table, filter } = useListStoreByKey({ key: pageKey });
const { setFilter, setTable, setTablePagination, setDisplayType } = useListStoreActions(); const { setFilter, setTable, setTablePagination, setDisplayType } = useListStoreActions();
const filter = useSongListFilter({ id, key: pageKey });
const { handleRefreshTable } = useListFilterRefresh({
itemType: LibraryItem.SONG,
server,
});
const cq = useContainerQuery(); const cq = useContainerQuery();
@ -99,48 +97,6 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
).find((f) => f.value === filter.sortBy)?.name) || ).find((f) => f.value === filter.sortBy)?.name) ||
'Unknown'; 'Unknown';
const handleFilterChange = useCallback(
async (filters?: SongListFilter) => {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
const pageFilters = filters || filter;
const query: SongListQuery = {
limit,
startIndex,
...pageFilters,
};
const queryKey = queryKeys.songs.list(server?.id || '', query);
const songsRes = await queryClient.fetchQuery(
queryKey,
async ({ signal }) =>
api.controller.getSongList({
apiClientProps: {
server,
signal,
},
query,
}),
{ cacheTime: 1000 * 60 * 1 },
);
params.successCallback(songsRes?.items || [], songsRes?.totalRecordCount || 0);
},
rowCount: undefined,
};
tableRef.current?.api.setDatasource(dataSource);
tableRef.current?.api.purgeInfiniteCache();
tableRef.current?.api.ensureIndexVisible(0, 'top');
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
},
[filter, pageKey, server, setTablePagination, tableRef],
);
const handleSetSortBy = useCallback( const handleSetSortBy = useCallback(
(e: MouseEvent<HTMLButtonElement>) => { (e: MouseEvent<HTMLButtonElement>) => {
if (!e.currentTarget?.value || !server?.type) return; if (!e.currentTarget?.value || !server?.type) return;
@ -150,6 +106,7 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
)?.defaultOrder; )?.defaultOrder;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { data: {
sortBy: e.currentTarget.value as SongListSort, sortBy: e.currentTarget.value as SongListSort,
sortOrder: sortOrder || SortOrder.ASC, sortOrder: sortOrder || SortOrder.ASC,
@ -158,9 +115,9 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters); handleRefreshTable(tableRef, updatedFilters);
}, },
[handleFilterChange, pageKey, server?.type, setFilter], [customFilters, handleRefreshTable, pageKey, server?.type, setFilter, tableRef],
); );
const handleSetMusicFolder = useCallback( const handleSetMusicFolder = useCallback(
@ -170,32 +127,35 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
let updatedFilters = null; let updatedFilters = null;
if (e.currentTarget.value === String(filter.musicFolderId)) { if (e.currentTarget.value === String(filter.musicFolderId)) {
updatedFilters = setFilter({ updatedFilters = setFilter({
customFilters,
data: { musicFolderId: undefined }, data: { musicFolderId: undefined },
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
} else { } else {
updatedFilters = setFilter({ updatedFilters = setFilter({
customFilters,
data: { musicFolderId: e.currentTarget.value }, data: { musicFolderId: e.currentTarget.value },
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
} }
handleFilterChange(updatedFilters); handleRefreshTable(tableRef, updatedFilters);
}, },
[filter.musicFolderId, handleFilterChange, setFilter, pageKey], [filter.musicFolderId, handleRefreshTable, tableRef, setFilter, customFilters, pageKey],
); );
const handleToggleSortOrder = useCallback(() => { const handleToggleSortOrder = useCallback(() => {
const newSortOrder = filter.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; const newSortOrder = filter.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters,
data: { sortOrder: newSortOrder }, data: { sortOrder: newSortOrder },
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
handleFilterChange(updatedFilters); handleRefreshTable(tableRef, updatedFilters);
}, [filter.sortOrder, handleFilterChange, pageKey, setFilter]); }, [customFilters, filter.sortOrder, handleRefreshTable, pageKey, setFilter, tableRef]);
const handleSetViewType = useCallback( const handleSetViewType = useCallback(
(e: MouseEvent<HTMLButtonElement>) => { (e: MouseEvent<HTMLButtonElement>) => {
@ -258,7 +218,14 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
const handleRefresh = () => { const handleRefresh = () => {
queryClient.invalidateQueries(queryKeys.songs.list(server?.id || '')); queryClient.invalidateQueries(queryKeys.songs.list(server?.id || ''));
handleFilterChange(filter); handleRefreshTable(tableRef, filter);
};
const onFilterChange = (filter: SongListFilter) => {
handleRefreshTable(tableRef, {
...filter,
...customFilters,
});
}; };
const handleOpenFiltersModal = () => { const handleOpenFiltersModal = () => {
@ -267,15 +234,15 @@ export const SongListHeaderFilters = ({ tableRef }: SongListHeaderFiltersProps)
<> <>
{server?.type === ServerType.NAVIDROME ? ( {server?.type === ServerType.NAVIDROME ? (
<NavidromeSongFilters <NavidromeSongFilters
handleFilterChange={handleFilterChange} customFilters={customFilters}
id={id}
pageKey={pageKey} pageKey={pageKey}
onFilterChange={onFilterChange}
/> />
) : ( ) : (
<JellyfinSongFilters <JellyfinSongFilters
handleFilterChange={handleFilterChange} customFilters={customFilters}
id={id}
pageKey={pageKey} pageKey={pageKey}
onFilterChange={onFilterChange}
/> />
)} )}
</> </>

View File

@ -1,24 +1,18 @@
import type { IDatasource } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Flex, Group, Stack } from '@mantine/core'; import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { ChangeEvent, MutableRefObject, useCallback } from 'react'; import { ChangeEvent, MutableRefObject } from 'react';
import { api } from '/@/renderer/api'; import { useListStoreByKey } from '../../../store/list.store';
import { queryKeys } from '/@/renderer/api/query-keys'; import { LibraryItem } from '/@/renderer/api/types';
import { LibraryItem, SongListQuery } from '/@/renderer/api/types';
import { PageHeader, SearchInput } from '/@/renderer/components'; import { PageHeader, SearchInput } from '/@/renderer/components';
import { useListContext } from '/@/renderer/context/list-context';
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared'; import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
import { SongListHeaderFilters } from '/@/renderer/features/songs/components/song-list-header-filters'; import { SongListHeaderFilters } from '/@/renderer/features/songs/components/song-list-header-filters';
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { queryClient } from '/@/renderer/lib/react-query'; import { useListFilterRefresh } from '/@/renderer/hooks/use-list-filter-refresh';
import { import { SongListFilter, useCurrentServer, useListStoreActions } from '/@/renderer/store';
SongListFilter,
useCurrentServer,
useListStoreActions,
useSongListFilter,
} from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { ListDisplayType } from '/@/renderer/types';
interface SongListHeaderProps { interface SongListHeaderProps {
itemCount?: number; itemCount?: number;
@ -28,62 +22,35 @@ interface SongListHeaderProps {
export const SongListHeader = ({ title, itemCount, tableRef }: SongListHeaderProps) => { export const SongListHeader = ({ title, itemCount, tableRef }: SongListHeaderProps) => {
const server = useCurrentServer(); const server = useCurrentServer();
const { id, pageKey, handlePlay } = useSongListContext(); const { pageKey, handlePlay, customFilters } = useListContext();
const filter = useSongListFilter({ id, key: pageKey });
const { setFilter, setTablePagination } = useListStoreActions(); const { setFilter, setTablePagination } = useListStoreActions();
const { display, filter } = useListStoreByKey({ key: pageKey });
const cq = useContainerQuery(); const cq = useContainerQuery();
const handleFilterChange = useCallback( const { handleRefreshTable } = useListFilterRefresh({
async (filters?: SongListFilter) => { itemType: LibraryItem.SONG,
const dataSource: IDatasource = { server,
getRows: async (params) => { });
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
const pageFilters = filters || filter;
const query: SongListQuery = {
limit,
startIndex,
...pageFilters,
};
const queryKey = queryKeys.songs.list(server?.id || '', query);
const songsRes = await queryClient.fetchQuery(
queryKey,
async ({ signal }) =>
api.controller.getSongList({
apiClientProps: {
server,
signal,
},
query,
}),
{ cacheTime: 1000 * 60 * 1 },
);
params.successCallback(songsRes?.items || [], songsRes?.totalRecordCount || 0);
},
rowCount: undefined,
};
tableRef.current?.api.setDatasource(dataSource);
tableRef.current?.api.purgeInfiniteCache();
tableRef.current?.api.ensureIndexVisible(0, 'top');
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
},
[filter, pageKey, server, setTablePagination, tableRef],
);
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => { const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
const previousSearchTerm = filter.searchTerm;
const searchTerm = e.target.value === '' ? undefined : e.target.value; const searchTerm = e.target.value === '' ? undefined : e.target.value;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
data: { searchTerm }, data: { searchTerm },
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
key: pageKey, key: pageKey,
}) as SongListFilter; }) as SongListFilter;
if (previousSearchTerm !== searchTerm) handleFilterChange(updatedFilters);
const filterWithCustom = {
...updatedFilters,
...customFilters,
};
if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) {
handleRefreshTable(tableRef, filterWithCustom);
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
} else {
// handleRefreshGrid(gridRef, filterWithCustom);
}
}, 500); }, 500);
const playButtonBehavior = usePlayButtonBehavior(); const playButtonBehavior = usePlayButtonBehavior();

View File

@ -1,25 +1,14 @@
import { RowDoubleClickedEvent } from '@ag-grid-community/core'; import { RowDoubleClickedEvent } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject } from 'react';
import { api } from '/@/renderer/api'; import { LibraryItem, QueueSong, SongListQuery } from '/@/renderer/api/types';
import { queryKeys } from '/@/renderer/api/query-keys';
import { LibraryItem, QueueSong, SongListQuery, SongListResponse } from '/@/renderer/api/types';
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid'; import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
import { VirtualTable } from '/@/renderer/components/virtual-table'; import { VirtualTable } from '/@/renderer/components/virtual-table';
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles'; import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
import { import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table';
AgGridFetchFn, import { useListContext } from '/@/renderer/context/list-context';
useVirtualTable,
} from '/@/renderer/components/virtual-table/hooks/use-virtual-table';
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { useSongListContext } from '/@/renderer/features/songs/context/song-list-context'; import { useCurrentServer, usePlayButtonBehavior } from '/@/renderer/store';
import {
useCurrentServer,
useListStoreActions,
usePlayButtonBehavior,
useSongListFilter,
useSongListStore,
} from '/@/renderer/store';
interface SongListTableViewProps { interface SongListTableViewProps {
itemCount?: number; itemCount?: number;
@ -28,50 +17,17 @@ interface SongListTableViewProps {
export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProps) => { export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProps) => {
const server = useCurrentServer(); const server = useCurrentServer();
const { id, pageKey, handlePlay } = useSongListContext(); const { pageKey, handlePlay, customFilters } = useListContext();
const filter = useSongListFilter({ id, key: pageKey });
const listProperties = useSongListStore({ id, key: pageKey });
const { setTable, setTablePagination } = useListStoreActions();
const fetchFn: AgGridFetchFn<SongListResponse, Omit<SongListQuery, 'startIndex'>> = useCallback(
async ({ filter, limit, startIndex }, signal) => {
const res = api.controller.getSongList({
apiClientProps: {
server,
signal,
},
query: {
...filter,
limit,
sortBy: filter.sortBy,
sortOrder: filter.sortOrder,
startIndex,
},
});
return res;
},
[server],
);
const { rowClassRules } = useCurrentSongRowStyles({ tableRef }); const { rowClassRules } = useCurrentSongRowStyles({ tableRef });
const tableProps = useVirtualTable<SongListResponse, Omit<SongListQuery, 'startIndex'>>({ const tableProps = useVirtualTable<SongListQuery>({
contextMenu: SONG_CONTEXT_MENU_ITEMS, contextMenu: SONG_CONTEXT_MENU_ITEMS,
fetch: { customFilters,
filter,
fn: fetchFn,
itemCount,
queryKey: queryKeys.albums.list,
server,
},
itemCount, itemCount,
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
pageKey, pageKey,
properties: listProperties, server,
setTable,
setTablePagination,
tableRef, tableRef,
}); });
@ -86,7 +42,7 @@ export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProp
<VirtualTable <VirtualTable
// https://github.com/ag-grid/ag-grid/issues/5284 // https://github.com/ag-grid/ag-grid/issues/5284
// Key is used to force remount of table when display, rowHeight, or server changes // Key is used to force remount of table when display, rowHeight, or server changes
key={`table-${listProperties.display}-${listProperties.table.rowHeight}-${server?.id}`} key={`table-${tableProps.rowHeight}-${server?.id}`}
ref={tableRef} ref={tableRef}
{...tableProps} {...tableProps}
rowClassRules={rowClassRules} rowClassRules={rowClassRules}

View File

@ -1,18 +0,0 @@
import { createContext, useContext } from 'react';
import { ListKey } from '/@/renderer/store';
import { Play } from '/@/renderer/types';
interface SongListContextProps {
handlePlay?: (args: { initialSongId?: string; playType: Play }) => void;
id?: string;
pageKey: ListKey;
}
export const SongListContext = createContext<SongListContextProps>({
pageKey: 'song',
});
export const useSongListContext = () => {
const ctxValue = useContext(SongListContext);
return ctxValue;
};

View File

@ -1,14 +1,14 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { useParams, useSearchParams } from 'react-router-dom'; import { useParams, useSearchParams } from 'react-router-dom';
import { SongListQuery, LibraryItem } from '/@/renderer/api/types'; import { LibraryItem, SongListQuery } from '/@/renderer/api/types';
import { ListContext } from '/@/renderer/context/list-context';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
import { AnimatedPage } from '/@/renderer/features/shared'; import { AnimatedPage } from '/@/renderer/features/shared';
import { SongListContent } from '/@/renderer/features/songs/components/song-list-content'; import { SongListContent } from '/@/renderer/features/songs/components/song-list-content';
import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header'; import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header';
import { SongListContext } from '/@/renderer/features/songs/context/song-list-context';
import { useSongList } from '/@/renderer/features/songs/queries/song-list-query'; import { useSongList } from '/@/renderer/features/songs/queries/song-list-query';
import { generatePageKey, useCurrentServer, useSongListFilter } from '/@/renderer/store'; import { useCurrentServer, useListFilterByKey } from '/@/renderer/store';
import { Play } from '/@/renderer/types'; import { Play } from '/@/renderer/types';
const TrackListRoute = () => { const TrackListRoute = () => {
@ -16,13 +16,18 @@ const TrackListRoute = () => {
const server = useCurrentServer(); const server = useCurrentServer();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { albumArtistId } = useParams(); const { albumArtistId } = useParams();
const pageKey = generatePageKey( const pageKey = albumArtistId ? `albumArtistSong` : 'song';
'song',
albumArtistId ? `${albumArtistId}_${server?.id}` : undefined, const customFilters = {
); ...(albumArtistId && { artistIds: [albumArtistId] }),
};
const handlePlayQueueAdd = usePlayQueueAdd(); const handlePlayQueueAdd = usePlayQueueAdd();
const songListFilter = useSongListFilter({ id: albumArtistId, key: pageKey }); const songListFilter = useListFilterByKey<SongListQuery>({
filter: customFilters,
key: pageKey,
});
const itemCountCheck = useSongList({ const itemCountCheck = useSongList({
options: { options: {
cacheTime: 1000 * 60, cacheTime: 1000 * 60,
@ -74,7 +79,7 @@ const TrackListRoute = () => {
return ( return (
<AnimatedPage> <AnimatedPage>
<SongListContext.Provider value={{ handlePlay, id: albumArtistId, pageKey }}> <ListContext.Provider value={{ customFilters, handlePlay, id: albumArtistId, pageKey }}>
<SongListHeader <SongListHeader
itemCount={itemCount} itemCount={itemCount}
tableRef={tableRef} tableRef={tableRef}
@ -84,7 +89,7 @@ const TrackListRoute = () => {
itemCount={itemCount} itemCount={itemCount}
tableRef={tableRef} tableRef={tableRef}
/> />
</SongListContext.Provider> </ListContext.Provider>
</AnimatedPage> </AnimatedPage>
); );
}; };