mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
refactor playlist route state
This commit is contained in:
@@ -13,6 +13,7 @@ interface ListContextProps {
|
||||
isSmartPlaylist?: boolean;
|
||||
itemCount?: number;
|
||||
listData?: unknown[];
|
||||
listKey?: ItemListKey;
|
||||
mode?: 'edit' | 'view';
|
||||
pageKey: ItemListKey | string;
|
||||
setDisplayMode?: (displayMode: ListDisplayMode) => void;
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useListContext } from '/@/renderer/context/list-context';
|
||||
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||
import { usePlaylistTrackList } from '/@/renderer/features/playlists/hooks/use-playlist-track-list';
|
||||
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
||||
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
||||
import { useCurrentServer, useGeneralSettings, useListSettings } from '/@/renderer/store';
|
||||
@@ -70,7 +71,6 @@ const PlaylistDetailSongListGrid = lazy(() =>
|
||||
export const PlaylistDetailSongListContent = () => {
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
const server = useCurrentServer();
|
||||
const { displayMode, setItemCount } = useListContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const playlistSongsQuery = useSuspenseQuery(
|
||||
@@ -82,18 +82,12 @@ export const PlaylistDetailSongListContent = () => {
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
displayMode !== LibraryItem.ALBUM &&
|
||||
playlistSongsQuery.data?.totalRecordCount != null
|
||||
) {
|
||||
setItemCount?.(playlistSongsQuery.data.totalRecordCount);
|
||||
}
|
||||
}, [displayMode, playlistSongsQuery.data?.totalRecordCount, setItemCount]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRefresh = async (payload: { key: string }) => {
|
||||
if (payload.key !== ItemListKey.PLAYLIST_SONG) {
|
||||
if (
|
||||
payload.key !== ItemListKey.PLAYLIST_SONG &&
|
||||
payload.key !== ItemListKey.PLAYLIST_ALBUM
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,7 +107,7 @@ export const PlaylistDetailSongListContent = () => {
|
||||
return () => {
|
||||
eventEmitter.off('ITEM_LIST_REFRESH', handleRefresh);
|
||||
};
|
||||
}, [playlistId, queryClient, server.id]);
|
||||
}, [playlistId, queryClient, server?.id]);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
@@ -124,7 +118,13 @@ export const PlaylistDetailSongListContent = () => {
|
||||
|
||||
export type OverridePlaylistSongListQuery = Omit<Partial<PlaylistSongListQuery>, 'id'>;
|
||||
|
||||
export const PlaylistDetailSongListView = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
interface PlaylistDetailSongListViewProps {
|
||||
data: PlaylistSongListResponse;
|
||||
/** When provided, table/grid use this instead of computing from data (avoids duplicate filter/sort). */
|
||||
items?: Song[];
|
||||
}
|
||||
|
||||
export const PlaylistDetailSongListView = ({ data, items }: PlaylistDetailSongListViewProps) => {
|
||||
const server = useCurrentServer();
|
||||
const { display, itemsPerPage, pagination, table } = useListSettings(ItemListKey.PLAYLIST_SONG);
|
||||
const { currentPage, onChange: onPageChange } = useItemListPagination();
|
||||
@@ -141,7 +141,12 @@ export const PlaylistDetailSongListView = ({ data }: { data: PlaylistSongListRes
|
||||
switch (display) {
|
||||
case ListDisplayType.GRID: {
|
||||
return (
|
||||
<PlaylistDetailSongListGrid data={data} serverId={server.id} {...paginationProps} />
|
||||
<PlaylistDetailSongListGrid
|
||||
data={data}
|
||||
items={items}
|
||||
serverId={server.id}
|
||||
{...paginationProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case ListDisplayType.TABLE: {
|
||||
@@ -155,6 +160,7 @@ export const PlaylistDetailSongListView = ({ data }: { data: PlaylistSongListRes
|
||||
enableHorizontalBorders={table.enableHorizontalBorders}
|
||||
enableRowHoverHighlight={table.enableRowHoverHighlight}
|
||||
enableVerticalBorders={table.enableVerticalBorders}
|
||||
items={items}
|
||||
serverId={server.id}
|
||||
size={table.size}
|
||||
{...paginationProps}
|
||||
@@ -363,7 +369,7 @@ export function playlistSongsToAlbums(songs: Song[]): PlaylistAlbumRow[] {
|
||||
return rows;
|
||||
}
|
||||
|
||||
const PlaylistDetailAlbumList = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
export const PlaylistDetailAlbumView = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
const player = usePlayer();
|
||||
const { setItemCount, setListData } = useListContext();
|
||||
const { detail, display, grid, itemsPerPage, pagination, table } = useListSettings(
|
||||
@@ -528,23 +534,33 @@ const PlaylistDetailAlbumList = ({ data }: { data: PlaylistSongListResponse }) =
|
||||
return renderAlbumList();
|
||||
};
|
||||
|
||||
const PlaylistDetailSongList = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
const { displayMode, isSmartPlaylist, mode } = useListContext();
|
||||
|
||||
if (displayMode === LibraryItem.ALBUM) {
|
||||
return <PlaylistDetailAlbumList data={data} />;
|
||||
}
|
||||
/** Track view: view mode uses centralized list derivation; edit mode uses local reorder state. */
|
||||
const PlaylistDetailTrackView = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
const { isSmartPlaylist, mode } = useListContext();
|
||||
|
||||
if (isSmartPlaylist) {
|
||||
return <PlaylistDetailSongListView data={data} />;
|
||||
return <PlaylistDetailTrackViewContent data={data} />;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 'edit':
|
||||
return <PlaylistDetailSongListEdit data={data} />;
|
||||
case 'view':
|
||||
return <PlaylistDetailSongListView data={data} />;
|
||||
default:
|
||||
return null;
|
||||
if (mode === 'edit') {
|
||||
return <PlaylistDetailSongListEdit data={data} />;
|
||||
}
|
||||
|
||||
return <PlaylistDetailTrackViewContent data={data} />;
|
||||
};
|
||||
|
||||
/** Uses usePlaylistTrackList once and passes derived items to the list view. */
|
||||
const PlaylistDetailTrackViewContent = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
const { sortedAndFilteredSongs } = usePlaylistTrackList(data);
|
||||
return <PlaylistDetailSongListView data={data} items={sortedAndFilteredSongs} />;
|
||||
};
|
||||
|
||||
const PlaylistDetailSongList = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
const { displayMode } = useListContext();
|
||||
|
||||
if (displayMode === LibraryItem.ALBUM) {
|
||||
return <PlaylistDetailAlbumView data={data} />;
|
||||
}
|
||||
|
||||
return <PlaylistDetailTrackView data={data} />;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
LibraryItem,
|
||||
PlaylistSongListQuery,
|
||||
PlaylistSongListResponse,
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ItemListKey } from '/@/shared/types/types';
|
||||
|
||||
@@ -23,36 +24,44 @@ interface PlaylistDetailSongListGridProps
|
||||
extends Omit<ItemListGridComponentProps<PlaylistSongListQuery>, 'query'> {
|
||||
currentPage?: number;
|
||||
data: PlaylistSongListResponse;
|
||||
items?: Song[];
|
||||
itemsPerPage?: number;
|
||||
onPageChange?: (page: number) => void;
|
||||
}
|
||||
|
||||
export const PlaylistDetailSongListGrid = forwardRef<any, PlaylistDetailSongListGridProps>(
|
||||
({ currentPage, data, itemsPerPage, onPageChange, saveScrollOffset = true }) => {
|
||||
({
|
||||
currentPage,
|
||||
data,
|
||||
items: itemsProp,
|
||||
itemsPerPage,
|
||||
onPageChange,
|
||||
saveScrollOffset = true,
|
||||
}) => {
|
||||
const { handleOnScrollEnd, scrollOffset } = useItemListScrollPersist({
|
||||
enabled: saveScrollOffset,
|
||||
});
|
||||
|
||||
const { searchTerm } = useSearchTermFilter();
|
||||
const { query } = usePlaylistSongListFilters();
|
||||
const { setListData } = useListContext();
|
||||
|
||||
const songData = useMemo(() => {
|
||||
let items = data?.items || [];
|
||||
|
||||
const songDataFromData = useMemo(() => {
|
||||
let list = data?.items || [];
|
||||
if (searchTerm) {
|
||||
items = searchLibraryItems(items, searchTerm, LibraryItem.SONG);
|
||||
return items;
|
||||
list = searchLibraryItems(list, searchTerm, LibraryItem.SONG);
|
||||
return list;
|
||||
}
|
||||
|
||||
return sortSongList(items, query.sortBy, query.sortOrder);
|
||||
return sortSongList(list, query.sortBy, query.sortOrder);
|
||||
}, [data?.items, searchTerm, query.sortBy, query.sortOrder]);
|
||||
|
||||
const { setListData } = useListContext();
|
||||
const songData = itemsProp ?? songDataFromData;
|
||||
|
||||
useEffect(() => {
|
||||
if (setListData) {
|
||||
setListData(songData);
|
||||
if (itemsProp == null && setListData) {
|
||||
setListData(songDataFromData);
|
||||
}
|
||||
}, [songData, setListData]);
|
||||
}, [itemsProp, songDataFromData, setListData]);
|
||||
|
||||
const gridProps = useListSettings(ItemListKey.PLAYLIST_SONG).grid;
|
||||
|
||||
|
||||
+7
-3
@@ -46,7 +46,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||
isSmartPlaylist,
|
||||
}: PlaylistDetailSongListHeaderFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { mode, setMode } = useListContext();
|
||||
const { listKey: listKeyFromContext, mode, setMode } = useListContext();
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
const playlistTarget = usePlaylistTarget();
|
||||
const { setPlaylistBehavior } = useSettingsStoreActions();
|
||||
@@ -66,8 +66,12 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||
});
|
||||
};
|
||||
|
||||
const isAlbumMode = playlistTarget === PlaylistTarget.ALBUM;
|
||||
const listKey = isAlbumMode ? ItemListKey.PLAYLIST_ALBUM : ItemListKey.PLAYLIST_SONG;
|
||||
const listKey =
|
||||
listKeyFromContext ??
|
||||
(playlistTarget === PlaylistTarget.ALBUM
|
||||
? ItemListKey.PLAYLIST_ALBUM
|
||||
: ItemListKey.PLAYLIST_SONG);
|
||||
const isAlbumMode = listKey === ItemListKey.PLAYLIST_ALBUM;
|
||||
const toggleChoice = isAlbumMode
|
||||
? t('entity.album', { count: 2, postProcess: 'titleCase' })
|
||||
: t('entity.track', { count: 2, postProcess: 'titleCase' });
|
||||
|
||||
@@ -27,6 +27,7 @@ interface PlaylistDetailSongListTableProps
|
||||
extends Omit<ItemListTableComponentProps<PlaylistSongListQuery>, 'query'> {
|
||||
currentPage?: number;
|
||||
data: PlaylistSongListResponse;
|
||||
items?: Song[];
|
||||
itemsPerPage?: number;
|
||||
onPageChange?: (page: number) => void;
|
||||
}
|
||||
@@ -44,6 +45,7 @@ export const PlaylistDetailSongListTable = forwardRef<any, PlaylistDetailSongLis
|
||||
enableRowHoverHighlight = true,
|
||||
enableSelection = true,
|
||||
enableVerticalBorders = false,
|
||||
items: itemsProp,
|
||||
itemsPerPage,
|
||||
onPageChange,
|
||||
saveScrollOffset = true,
|
||||
@@ -65,24 +67,24 @@ export const PlaylistDetailSongListTable = forwardRef<any, PlaylistDetailSongLis
|
||||
|
||||
const { searchTerm } = useSearchTermFilter();
|
||||
const { query } = usePlaylistSongListFilters();
|
||||
const { setListData } = useListContext();
|
||||
|
||||
const songData = useMemo(() => {
|
||||
let items = data?.items || [];
|
||||
|
||||
const songDataFromData = useMemo(() => {
|
||||
let list = data?.items || [];
|
||||
if (searchTerm) {
|
||||
items = searchLibraryItems(items, searchTerm, LibraryItem.SONG);
|
||||
return items;
|
||||
list = searchLibraryItems(list, searchTerm, LibraryItem.SONG);
|
||||
return list;
|
||||
}
|
||||
|
||||
return sortSongList(items, query.sortBy, query.sortOrder);
|
||||
return sortSongList(list, query.sortBy, query.sortOrder);
|
||||
}, [data?.items, searchTerm, query.sortBy, query.sortOrder]);
|
||||
|
||||
const { setListData } = useListContext();
|
||||
const songData = itemsProp ?? songDataFromData;
|
||||
|
||||
useEffect(() => {
|
||||
if (setListData) {
|
||||
setListData(songData);
|
||||
if (itemsProp == null && setListData) {
|
||||
setListData(songDataFromData);
|
||||
}
|
||||
}, [songData, setListData]);
|
||||
}, [itemsProp, songDataFromData, setListData]);
|
||||
|
||||
const player = usePlayer();
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { useListContext } from '/@/renderer/context/list-context';
|
||||
import { usePlaylistSongListFilters } from '/@/renderer/features/playlists/hooks/use-playlist-song-list-filters';
|
||||
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
||||
import { searchLibraryItems } from '/@/renderer/features/shared/utils';
|
||||
import { sortSongList } from '/@/shared/api/utils';
|
||||
import { LibraryItem, PlaylistSongListResponse, Song } from '/@/shared/types/domain-types';
|
||||
|
||||
export function usePlaylistTrackList(data: PlaylistSongListResponse | undefined): {
|
||||
sortedAndFilteredSongs: Song[];
|
||||
totalCount: number;
|
||||
} {
|
||||
const { setItemCount, setListData } = useListContext();
|
||||
const { searchTerm } = useSearchTermFilter();
|
||||
const { query } = usePlaylistSongListFilters();
|
||||
|
||||
const sortedAndFilteredSongs = useMemo(() => {
|
||||
const raw = data?.items ?? [];
|
||||
|
||||
if (searchTerm) {
|
||||
return searchLibraryItems(raw, searchTerm, LibraryItem.SONG);
|
||||
}
|
||||
|
||||
return sortSongList(raw, query.sortBy, query.sortOrder);
|
||||
}, [data?.items, searchTerm, query.sortBy, query.sortOrder]);
|
||||
|
||||
const totalCount = sortedAndFilteredSongs.length;
|
||||
|
||||
useEffect(() => {
|
||||
setListData?.(sortedAndFilteredSongs);
|
||||
setItemCount?.(totalCount);
|
||||
}, [sortedAndFilteredSongs, totalCount, setListData, setItemCount]);
|
||||
|
||||
return { sortedAndFilteredSongs, totalCount };
|
||||
}
|
||||
@@ -402,6 +402,8 @@ const PlaylistDetailSongListRoute = () => {
|
||||
const playlistTarget = usePlaylistTarget();
|
||||
const displayMode: LibraryItem.ALBUM | LibraryItem.SONG =
|
||||
playlistTarget === PlaylistTarget.ALBUM ? LibraryItem.ALBUM : LibraryItem.SONG;
|
||||
const listKey =
|
||||
displayMode === LibraryItem.ALBUM ? ItemListKey.PLAYLIST_ALBUM : ItemListKey.PLAYLIST_SONG;
|
||||
|
||||
const [itemCount, setItemCount] = useState<number | undefined>(undefined);
|
||||
const [listData, setListData] = useState<unknown[]>([]);
|
||||
@@ -415,13 +417,14 @@ const PlaylistDetailSongListRoute = () => {
|
||||
isSmartPlaylist,
|
||||
itemCount,
|
||||
listData,
|
||||
listKey,
|
||||
mode,
|
||||
pageKey: ItemListKey.PLAYLIST_SONG,
|
||||
pageKey: listKey,
|
||||
setItemCount,
|
||||
setListData,
|
||||
setMode,
|
||||
};
|
||||
}, [playlistId, isSmartPlaylist, displayMode, itemCount, listData, mode]);
|
||||
}, [playlistId, isSmartPlaylist, displayMode, listKey, itemCount, listData, mode]);
|
||||
|
||||
return (
|
||||
<AnimatedPage key={`playlist-detail-songList-${playlistId}`}>
|
||||
|
||||
Reference in New Issue
Block a user