mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 10:03:33 +00:00
playlist sort and refactoring
This commit is contained in:
parent
1cbb3e56bc
commit
306167fee3
3 changed files with 63 additions and 57 deletions
|
|
@ -3,20 +3,20 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li
|
|||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { MouseEvent, MutableRefObject, useCallback } from 'react';
|
||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form';
|
||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||
import { OrderToggleButton } from '/@/renderer/features/shared';
|
||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
||||
import { SearchInput } from '/@/renderer/features/shared/components/search-input';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import {
|
||||
|
|
@ -37,13 +37,7 @@ import { Icon } from '/@/shared/components/icon/icon';
|
|||
import { ConfirmModal } from '/@/shared/components/modal/modal';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import {
|
||||
LibraryItem,
|
||||
PlaylistSongListQueryClientSide,
|
||||
ServerType,
|
||||
SongListSort,
|
||||
SortOrder,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ServerType, SongListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
import { ListDisplayType, Play } from '/@/shared/types/types';
|
||||
|
||||
const FILTERS = {
|
||||
|
|
@ -246,11 +240,13 @@ const FILTERS = {
|
|||
};
|
||||
|
||||
interface PlaylistDetailSongListHeaderFiltersProps {
|
||||
handlePlay: (playType: Play) => void;
|
||||
handleToggleShowQueryBuilder: () => void;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const PlaylistDetailSongListHeaderFilters = ({
|
||||
handlePlay,
|
||||
handleToggleShowQueryBuilder,
|
||||
tableRef,
|
||||
}: PlaylistDetailSongListHeaderFiltersProps) => {
|
||||
|
|
@ -262,16 +258,13 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
const setPage = useSetPlaylistStore();
|
||||
const setFilter = useSetPlaylistDetailFilters();
|
||||
const page = usePlaylistDetailStore();
|
||||
const filters: Partial<PlaylistSongListQueryClientSide> = {
|
||||
sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID,
|
||||
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
|
||||
};
|
||||
const searchTerm = page?.table.id[playlistId]?.filter?.searchTerm;
|
||||
const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID;
|
||||
const sortOrder = page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC;
|
||||
|
||||
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||
const isSmartPlaylist = detailQuery.data?.rules;
|
||||
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
|
||||
const cq = useContainerQuery();
|
||||
|
||||
const setPagination = useSetPlaylistTablePagination();
|
||||
|
|
@ -279,8 +272,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
|
||||
const sortByLabel =
|
||||
(server?.type &&
|
||||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filters.sortBy)
|
||||
?.name) ||
|
||||
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === sortBy)?.name) ||
|
||||
'Unknown';
|
||||
|
||||
const handleItemSize = (e: number) => {
|
||||
|
|
@ -307,13 +299,13 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
(e: MouseEvent<HTMLButtonElement>) => {
|
||||
if (!e.currentTarget?.value || !server?.type) return;
|
||||
|
||||
const sortOrder = FILTERS[server.type as keyof typeof FILTERS].find(
|
||||
const newSortOrder = FILTERS[server.type as keyof typeof FILTERS].find(
|
||||
(f) => f.value === e.currentTarget.value,
|
||||
)?.defaultOrder;
|
||||
|
||||
setFilter(playlistId, {
|
||||
sortBy: e.currentTarget.value as SongListSort,
|
||||
sortOrder: sortOrder || SortOrder.ASC,
|
||||
sortOrder: newSortOrder || SortOrder.ASC,
|
||||
});
|
||||
|
||||
handleFilterChange();
|
||||
|
|
@ -322,10 +314,15 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
);
|
||||
|
||||
const handleToggleSortOrder = useCallback(() => {
|
||||
const newSortOrder = filters.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
||||
const newSortOrder = sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
||||
setFilter(playlistId, { sortOrder: newSortOrder });
|
||||
handleFilterChange();
|
||||
}, [filters.sortOrder, handleFilterChange, playlistId, setFilter]);
|
||||
}, [sortOrder, handleFilterChange, playlistId, setFilter]);
|
||||
|
||||
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilter(playlistId, { searchTerm: e.target.value });
|
||||
handleFilterChange();
|
||||
}, 500);
|
||||
|
||||
const handleSetViewType = useCallback(
|
||||
(displayType: ListDisplayType) => {
|
||||
|
|
@ -370,14 +367,6 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handlePlay = async (playType: Play) => {
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: { id: [playlistId], type: LibraryItem.PLAYLIST },
|
||||
playType,
|
||||
query: filters,
|
||||
});
|
||||
};
|
||||
|
||||
const deletePlaylistMutation = useDeletePlaylist({});
|
||||
|
||||
const handleDeletePlaylist = useCallback(() => {
|
||||
|
|
@ -427,7 +416,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
<DropdownMenu.Dropdown>
|
||||
{FILTERS[server?.type as keyof typeof FILTERS].map((filter) => (
|
||||
<DropdownMenu.Item
|
||||
isSelected={filter.value === filters.sortBy}
|
||||
isSelected={filter.value === sortBy}
|
||||
key={`filter-${filter.name}`}
|
||||
onClick={handleSetSortBy}
|
||||
value={filter.value}
|
||||
|
|
@ -441,7 +430,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filters.sortOrder || SortOrder.ASC}
|
||||
sortOrder={sortOrder || SortOrder.ASC}
|
||||
/>
|
||||
<DropdownMenu position="bottom-start">
|
||||
<DropdownMenu.Target>
|
||||
|
|
@ -503,6 +492,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
)}
|
||||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
<SearchInput defaultValue={searchTerm} onChange={handleSearch} />
|
||||
</Group>
|
||||
<Group>
|
||||
<ListConfigMenu
|
||||
|
|
|
|||
|
|
@ -5,31 +5,27 @@ import { useTranslation } from 'react-i18next';
|
|||
import { useParams } from 'react-router';
|
||||
|
||||
import { PageHeader } from '/@/renderer/components/page-header/page-header';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { PlaylistDetailSongListHeaderFilters } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header-filters';
|
||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||
import { useCurrentServer, usePlaylistDetailStore } from '/@/renderer/store';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import { formatDurationString } from '/@/renderer/utils';
|
||||
import { Badge } from '/@/shared/components/badge/badge';
|
||||
import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import {
|
||||
LibraryItem,
|
||||
PlaylistSongListQueryClientSide,
|
||||
SongListSort,
|
||||
SortOrder,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
interface PlaylistDetailHeaderProps {
|
||||
handlePlay: (playType: Play) => void;
|
||||
|
||||
handleToggleShowQueryBuilder: () => void;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const PlaylistDetailSongListHeader = ({
|
||||
handlePlay,
|
||||
handleToggleShowQueryBuilder,
|
||||
itemCount,
|
||||
tableRef,
|
||||
|
|
@ -38,20 +34,6 @@ export const PlaylistDetailSongListHeader = ({
|
|||
const { playlistId } = useParams() as { playlistId: string };
|
||||
const server = useCurrentServer();
|
||||
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const page = usePlaylistDetailStore();
|
||||
const filters: Partial<PlaylistSongListQueryClientSide> = {
|
||||
sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID,
|
||||
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
|
||||
};
|
||||
|
||||
const handlePlay = async (playType: Play) => {
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: { id: [playlistId], type: LibraryItem.PLAYLIST },
|
||||
playType,
|
||||
query: filters,
|
||||
});
|
||||
};
|
||||
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
|
|
@ -78,6 +60,7 @@ export const PlaylistDetailSongListHeader = ({
|
|||
</PageHeader>
|
||||
<FilterBar>
|
||||
<PlaylistDetailSongListHeaderFilters
|
||||
handlePlay={handlePlay}
|
||||
handleToggleShowQueryBuilder={handleToggleShowQueryBuilder}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import Fuse from 'fuse.js';
|
||||
import { motion } from 'motion/react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { generatePath, useNavigate, useParams } from 'react-router';
|
||||
|
||||
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
||||
import { PlaylistDetailSongListContent } from '/@/renderer/features/playlists/components/playlist-detail-song-list-content';
|
||||
import { PlaylistDetailSongListHeader } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header';
|
||||
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
|
||||
|
|
@ -23,6 +25,7 @@ import { Group } from '/@/shared/components/group/group';
|
|||
import { Text } from '/@/shared/components/text/text';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { ServerType, SongListSort, SortOrder, sortSongList } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
const PlaylistDetailSongListRoute = () => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -30,6 +33,7 @@ const PlaylistDetailSongListRoute = () => {
|
|||
const tableRef = useRef<AgGridReactType | null>(null);
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
const server = useCurrentServer();
|
||||
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
||||
|
||||
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
||||
const createPlaylistMutation = useCreatePlaylist({});
|
||||
|
|
@ -151,21 +155,50 @@ const PlaylistDetailSongListRoute = () => {
|
|||
serverId: server?.id,
|
||||
});
|
||||
|
||||
const itemCount = playlistSongs.data?.totalRecordCount ?? undefined;
|
||||
|
||||
const filterSortedSongs = useMemo(() => {
|
||||
if (playlistSongs.data?.items) {
|
||||
let items = playlistSongs.data?.items;
|
||||
|
||||
if (items) {
|
||||
const searchTerm = page?.table.id[playlistId]?.filter.searchTerm;
|
||||
|
||||
if (searchTerm) {
|
||||
const fuse = new Fuse(items, {
|
||||
fieldNormWeight: 1,
|
||||
ignoreLocation: true,
|
||||
keys: [
|
||||
'name',
|
||||
'album',
|
||||
{
|
||||
getFn: (song) => song.artists.map((artist) => artist.name),
|
||||
name: 'artist',
|
||||
},
|
||||
],
|
||||
threshold: 0,
|
||||
});
|
||||
items = fuse.search(searchTerm).map((item) => item.item);
|
||||
}
|
||||
|
||||
const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID;
|
||||
const sortOrder = page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC;
|
||||
return sortSongList(playlistSongs.data?.items, sortBy, sortOrder);
|
||||
return sortSongList(items, sortBy, sortOrder);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [playlistSongs.data?.items, page?.table.id, playlistId]);
|
||||
|
||||
const itemCount = playlistSongs.data?.totalRecordCount ? filterSortedSongs.length : undefined;
|
||||
|
||||
const handlePlay = (play: Play) => {
|
||||
handlePlayQueueAdd?.({
|
||||
byData: filterSortedSongs,
|
||||
playType: play,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedPage key={`playlist-detail-songList-${playlistId}`}>
|
||||
<PlaylistDetailSongListHeader
|
||||
handlePlay={handlePlay}
|
||||
handleToggleShowQueryBuilder={handleToggleShowQueryBuilder}
|
||||
itemCount={itemCount}
|
||||
tableRef={tableRef}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue