mirror of
https://github.com/jeffvli/feishin.git
synced 2024-11-20 14:37:06 +01:00
Update song list implementation
This commit is contained in:
parent
85964bfded
commit
6cd27c3e88
@ -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 (
|
||||||
|
@ -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 (
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -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();
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
|
||||||
};
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user