mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-21 03:14:16 +02:00
add support for full playlist re-order (#1327)
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { lazy, Suspense, useEffect } from 'react';
|
||||
import { lazy, Suspense, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||
import { useListContext } from '/@/renderer/context/list-context';
|
||||
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||
import { ItemListSettings, useCurrentServer, useListSettings } from '/@/renderer/store';
|
||||
import { PlaylistDetailSongListEditTable } from '/@/renderer/features/playlists/components/playlist-detail-song-list-table';
|
||||
import { useCurrentServer, useListSettings } from '/@/renderer/store';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
import { PlaylistSongListQuery, PlaylistSongListResponse } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, ListDisplayType } from '/@/shared/types/types';
|
||||
import { ItemListKey, ListDisplayType, TableColumn } from '/@/shared/types/types';
|
||||
|
||||
const PlaylistDetailSongListTable = lazy(() =>
|
||||
import('/@/renderer/features/playlists/components/playlist-detail-song-list-table').then(
|
||||
@@ -19,9 +21,6 @@ const PlaylistDetailSongListTable = lazy(() =>
|
||||
);
|
||||
|
||||
export const PlaylistDetailSongListContent = () => {
|
||||
const { display, grid, itemsPerPage, pagination, table } = useListSettings(
|
||||
ItemListKey.PLAYLIST_SONG,
|
||||
);
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
const server = useCurrentServer();
|
||||
const { setItemCount } = useListContext();
|
||||
@@ -71,28 +70,16 @@ export const PlaylistDetailSongListContent = () => {
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
<PlaylistDetailSongListView
|
||||
data={playlistSongsQuery.data}
|
||||
display={display}
|
||||
grid={grid}
|
||||
itemsPerPage={itemsPerPage}
|
||||
pagination={pagination}
|
||||
table={table}
|
||||
/>
|
||||
<PlaylistDetailSongList data={playlistSongsQuery.data} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export type OverridePlaylistSongListQuery = Omit<Partial<PlaylistSongListQuery>, 'id'>;
|
||||
|
||||
export const PlaylistDetailSongListView = ({
|
||||
data,
|
||||
display,
|
||||
table,
|
||||
}: ItemListSettings & {
|
||||
data: PlaylistSongListResponse;
|
||||
}) => {
|
||||
export const PlaylistDetailSongListView = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
const server = useCurrentServer();
|
||||
const { display, table } = useListSettings(ItemListKey.PLAYLIST_SONG);
|
||||
|
||||
switch (display) {
|
||||
case ListDisplayType.TABLE: {
|
||||
@@ -114,3 +101,149 @@ export const PlaylistDetailSongListView = ({
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const PlaylistDetailSongListEdit = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
const server = useCurrentServer();
|
||||
const { display, table } = useListSettings(ItemListKey.PLAYLIST_SONG);
|
||||
|
||||
const [localData, setLocalData] = useState<PlaylistSongListResponse>(data);
|
||||
|
||||
const tableRef = useRef<ItemListHandle | null>(null);
|
||||
|
||||
// Listen for playlist reorder events
|
||||
useEffect(() => {
|
||||
const handleReorder = (payload: {
|
||||
edge: 'bottom' | 'top' | null;
|
||||
playlistId: string;
|
||||
sourceIds: string[];
|
||||
targetId: string;
|
||||
}) => {
|
||||
// Only handle events for this playlist
|
||||
if (payload.playlistId !== playlistId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLocalData((prev) => {
|
||||
if (!prev?.items || !payload.edge) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
// Create a list of IDs in current order
|
||||
const currentIds = prev.items.map((item) => item.id);
|
||||
|
||||
// Find the target index
|
||||
const targetIndex = currentIds.indexOf(payload.targetId);
|
||||
if (targetIndex === -1) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
// Remove all source IDs from their current positions
|
||||
const idsWithoutSources = currentIds.filter(
|
||||
(id) => !payload.sourceIds.includes(id),
|
||||
);
|
||||
|
||||
// Calculate the insertion index based on the original target position
|
||||
const sourcesBeforeTarget = payload.sourceIds.filter((id) => {
|
||||
const sourceIndex = currentIds.indexOf(id);
|
||||
return sourceIndex !== -1 && sourceIndex < targetIndex;
|
||||
}).length;
|
||||
|
||||
// Calculate the insert index in the filtered list
|
||||
const insertIndexInFiltered =
|
||||
payload.edge === 'top'
|
||||
? targetIndex - sourcesBeforeTarget
|
||||
: targetIndex - sourcesBeforeTarget + 1;
|
||||
|
||||
// Ensure insertIndex is within bounds
|
||||
const insertIndex = Math.max(
|
||||
0,
|
||||
Math.min(insertIndexInFiltered, idsWithoutSources.length),
|
||||
);
|
||||
|
||||
// Insert source IDs at the calculated position
|
||||
const reorderedIds = [
|
||||
...idsWithoutSources.slice(0, insertIndex),
|
||||
...payload.sourceIds,
|
||||
...idsWithoutSources.slice(insertIndex),
|
||||
];
|
||||
|
||||
// Create a map for quick lookup
|
||||
const itemMap = new Map(prev.items.map((item) => [item.id, item]));
|
||||
|
||||
// Reorder items based on new ID order
|
||||
const reorderedItems = reorderedIds
|
||||
.map((id) => itemMap.get(id))
|
||||
.filter((item): item is NonNullable<typeof item> => item !== undefined);
|
||||
|
||||
return {
|
||||
...prev,
|
||||
items: reorderedItems,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
eventEmitter.on('PLAYLIST_REORDER', handleReorder);
|
||||
|
||||
return () => {
|
||||
eventEmitter.off('PLAYLIST_REORDER', handleReorder);
|
||||
};
|
||||
}, [playlistId]);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
align: 'center' as 'center' | 'end' | 'start',
|
||||
id: TableColumn.PLAYLIST_REORDER,
|
||||
isEnabled: true,
|
||||
pinned: 'left' as 'left' | 'right' | null,
|
||||
width: 100,
|
||||
},
|
||||
...table.columns,
|
||||
];
|
||||
}, [table.columns]);
|
||||
|
||||
const { setListData } = useListContext();
|
||||
|
||||
useEffect(() => {
|
||||
setListData?.(localData.items);
|
||||
}, [localData, setListData]);
|
||||
|
||||
switch (display) {
|
||||
case ListDisplayType.TABLE: {
|
||||
return (
|
||||
<PlaylistDetailSongListEditTable
|
||||
autoFitColumns={table.autoFitColumns}
|
||||
columns={columns}
|
||||
data={localData}
|
||||
enableAlternateRowColors={table.enableAlternateRowColors}
|
||||
enableHorizontalBorders={table.enableHorizontalBorders}
|
||||
enableRowHoverHighlight={table.enableRowHoverHighlight}
|
||||
enableVerticalBorders={table.enableVerticalBorders}
|
||||
ref={tableRef}
|
||||
serverId={server.id}
|
||||
size={table.size}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const PlaylistDetailSongList = ({ data }: { data: PlaylistSongListResponse }) => {
|
||||
const { isSmartPlaylist, mode } = useListContext();
|
||||
|
||||
if (isSmartPlaylist) {
|
||||
return <PlaylistDetailSongListView data={data} />;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 'edit':
|
||||
return <PlaylistDetailSongListEdit data={data} />;
|
||||
case 'view':
|
||||
return <PlaylistDetailSongListView data={data} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user