diff --git a/src/renderer/context/list-context.tsx b/src/renderer/context/list-context.tsx
index 7f0f46bbf..f07cad07a 100644
--- a/src/renderer/context/list-context.tsx
+++ b/src/renderer/context/list-context.tsx
@@ -13,6 +13,7 @@ interface ListContextProps {
isSmartPlaylist?: boolean;
itemCount?: number;
listData?: unknown[];
+ listKey?: ItemListKey;
mode?: 'edit' | 'view';
pageKey: ItemListKey | string;
setDisplayMode?: (displayMode: ListDisplayMode) => void;
diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx
index dda37b632..7203da4cc 100644
--- a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx
+++ b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx
@@ -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 (
}>
@@ -124,7 +118,13 @@ export const PlaylistDetailSongListContent = () => {
export type OverridePlaylistSongListQuery = Omit, '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 (
-
+
);
}
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 ;
- }
+/** 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 ;
+ return ;
}
- switch (mode) {
- case 'edit':
- return ;
- case 'view':
- return ;
- default:
- return null;
+ if (mode === 'edit') {
+ return ;
}
+
+ return ;
+};
+
+/** Uses usePlaylistTrackList once and passes derived items to the list view. */
+const PlaylistDetailTrackViewContent = ({ data }: { data: PlaylistSongListResponse }) => {
+ const { sortedAndFilteredSongs } = usePlaylistTrackList(data);
+ return ;
+};
+
+const PlaylistDetailSongList = ({ data }: { data: PlaylistSongListResponse }) => {
+ const { displayMode } = useListContext();
+
+ if (displayMode === LibraryItem.ALBUM) {
+ return ;
+ }
+
+ return ;
};
diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-grid.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-grid.tsx
index ebc847084..2500df74b 100644
--- a/src/renderer/features/playlists/components/playlist-detail-song-list-grid.tsx
+++ b/src/renderer/features/playlists/components/playlist-detail-song-list-grid.tsx
@@ -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, 'query'> {
currentPage?: number;
data: PlaylistSongListResponse;
+ items?: Song[];
itemsPerPage?: number;
onPageChange?: (page: number) => void;
}
export const PlaylistDetailSongListGrid = forwardRef(
- ({ 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;
diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx
index 72450e78e..b7f5076d8 100644
--- a/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx
+++ b/src/renderer/features/playlists/components/playlist-detail-song-list-header-filters.tsx
@@ -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' });
diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-table.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-table.tsx
index f27672fb8..14989fe15 100644
--- a/src/renderer/features/playlists/components/playlist-detail-song-list-table.tsx
+++ b/src/renderer/features/playlists/components/playlist-detail-song-list-table.tsx
@@ -27,6 +27,7 @@ interface PlaylistDetailSongListTableProps
extends Omit, 'query'> {
currentPage?: number;
data: PlaylistSongListResponse;
+ items?: Song[];
itemsPerPage?: number;
onPageChange?: (page: number) => void;
}
@@ -44,6 +45,7 @@ export const PlaylistDetailSongListTable = forwardRef {
- 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();
diff --git a/src/renderer/features/playlists/hooks/use-playlist-track-list.ts b/src/renderer/features/playlists/hooks/use-playlist-track-list.ts
new file mode 100644
index 000000000..c5f961cd4
--- /dev/null
+++ b/src/renderer/features/playlists/hooks/use-playlist-track-list.ts
@@ -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 };
+}
diff --git a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx
index 0abda7d43..fbf839785 100644
--- a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx
+++ b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx
@@ -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(undefined);
const [listData, setListData] = useState([]);
@@ -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 (