feishin/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx

272 lines
8.8 KiB
TypeScript
Raw Normal View History

2022-12-31 18:03:26 -08:00
import { MutableRefObject, useCallback, useMemo } from 'react';
import type {
BodyScrollEvent,
CellContextMenuEvent,
ColDef,
2023-01-01 13:58:05 -08:00
GridReadyEvent,
IDatasource,
PaginationChangedEvent,
2022-12-31 18:03:26 -08:00
RowDoubleClickedEvent,
} from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
2023-01-01 13:58:05 -08:00
import { getColumnDefs, TablePagination, VirtualTable } from '/@/renderer/components';
import {
useCurrentServer,
usePlaylistDetailStore,
usePlaylistDetailTablePagination,
useSetPlaylistDetailTable,
useSetPlaylistDetailTablePagination,
} from '/@/renderer/store';
import { LibraryItem, ListDisplayType } from '/@/renderer/types';
import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion';
2022-12-31 18:03:26 -08:00
import debounce from 'lodash/debounce';
import { openContextMenu } from '/@/renderer/features/context-menu';
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import sortBy from 'lodash/sortBy';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
2023-01-01 13:58:05 -08:00
import { PlaylistSongListQuery, QueueSong, SongListSort, SortOrder } from '/@/renderer/api/types';
2022-12-31 18:03:26 -08:00
import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
import { useParams } from 'react-router';
2022-12-31 19:26:58 -08:00
import { usePlayQueueAdd } from '/@/renderer/features/player';
2023-01-01 13:58:05 -08:00
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
2022-12-31 18:03:26 -08:00
interface PlaylistDetailContentProps {
tableRef: MutableRefObject<AgGridReactType | null>;
}
2023-01-01 13:58:05 -08:00
export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailContentProps) => {
2022-12-31 18:03:26 -08:00
const { playlistId } = useParams() as { playlistId: string };
2023-01-01 13:58:05 -08:00
const queryClient = useQueryClient();
2022-12-31 18:03:26 -08:00
const server = useCurrentServer();
2023-01-01 13:58:05 -08:00
const page = usePlaylistDetailStore();
const filters: Partial<PlaylistSongListQuery> = useMemo(() => {
return {
sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID,
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
};
}, [page?.table.id, playlistId]);
const p = usePlaylistDetailTablePagination(playlistId);
const pagination = {
currentPage: p?.currentPage || 0,
itemsPerPage: p?.itemsPerPage || 100,
scrollOffset: p?.scrollOffset || 0,
totalItems: p?.totalItems || 1,
totalPages: p?.totalPages || 1,
};
2022-12-31 18:03:26 -08:00
2023-01-01 13:58:05 -08:00
const setPagination = useSetPlaylistDetailTablePagination();
const setTable = useSetPlaylistDetailTable();
2022-12-31 19:26:58 -08:00
const handlePlayQueueAdd = usePlayQueueAdd();
2022-12-31 18:03:26 -08:00
const playButtonBehavior = usePlayButtonBehavior();
2023-01-01 13:58:05 -08:00
const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED;
2022-12-31 18:03:26 -08:00
2023-01-01 13:58:05 -08:00
const checkPlaylistList = usePlaylistSongList({
2022-12-31 18:03:26 -08:00
id: playlistId,
2023-01-01 13:58:05 -08:00
limit: 1,
2022-12-31 18:03:26 -08:00
startIndex: 0,
});
const columnDefs: ColDef[] = useMemo(
() => getColumnDefs(page.table.columns),
[page.table.columns],
);
const defaultColumnDefs: ColDef = useMemo(() => {
return {
lockPinned: true,
lockVisible: true,
resizable: true,
};
}, []);
2023-01-01 13:58:05 -08:00
const onGridReady = useCallback(
(params: GridReadyEvent) => {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId, {
id: playlistId,
limit,
startIndex,
...filters,
});
const songsRes = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
api.controller.getPlaylistSongList({
query: {
id: playlistId,
limit,
startIndex,
...filters,
},
server,
signal,
}),
);
const songs = api.normalize.songList(songsRes, server);
params.successCallback(songs?.items || [], songsRes?.totalRecordCount);
},
rowCount: undefined,
};
params.api.setDatasource(dataSource);
params.api.ensureIndexVisible(pagination.scrollOffset, 'top');
},
[filters, pagination.scrollOffset, playlistId, queryClient, server],
);
const onPaginationChanged = useCallback(
(event: PaginationChangedEvent) => {
if (!isPaginationEnabled || !event.api) return;
// Scroll to top of page on pagination change
const currentPageStartIndex = pagination.currentPage * pagination.itemsPerPage;
event.api?.ensureIndexVisible(currentPageStartIndex, 'top');
setPagination(playlistId, {
itemsPerPage: event.api.paginationGetPageSize(),
totalItems: event.api.paginationGetRowCount(),
totalPages: event.api.paginationGetTotalPages() + 1,
});
},
[
isPaginationEnabled,
pagination.currentPage,
pagination.itemsPerPage,
playlistId,
setPagination,
],
);
2022-12-31 18:03:26 -08:00
const handleGridSizeChange = () => {
if (page.table.autoFit) {
tableRef?.current?.api.sizeColumnsToFit();
}
};
const handleColumnChange = useCallback(() => {
const { columnApi } = tableRef?.current || {};
const columnsOrder = columnApi?.getAllGridColumns();
if (!columnsOrder) return;
const columnsInSettings = page.table.columns;
const updatedColumns = [];
for (const column of columnsOrder) {
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
if (columnInSettings) {
updatedColumns.push({
...columnInSettings,
...(!page.table.autoFit && {
width: column.getActualWidth(),
}),
});
}
}
setTable({ columns: updatedColumns });
}, [page.table.autoFit, page.table.columns, setTable, tableRef]);
const debouncedColumnChange = debounce(handleColumnChange, 200);
const handleScroll = (e: BodyScrollEvent) => {
const scrollOffset = Number((e.top / page.table.rowHeight).toFixed(0));
2023-01-01 13:58:05 -08:00
setPagination(playlistId, { scrollOffset });
2022-12-31 18:03:26 -08:00
};
const handleContextMenu = (e: CellContextMenuEvent) => {
if (!e.event) return;
const clickEvent = e.event as MouseEvent;
clickEvent.preventDefault();
const selectedNodes = e.api.getSelectedNodes();
const selectedIds = selectedNodes.map((node) => node.data.id);
let selectedRows = sortBy(selectedNodes, ['rowIndex']).map((node) => node.data);
if (!selectedIds.includes(e.data.id)) {
e.api.deselectAll();
e.node.setSelected(true);
selectedRows = [e.data];
}
openContextMenu({
data: selectedRows,
menuItems: SONG_CONTEXT_MENU_ITEMS,
type: LibraryItem.SONG,
xPos: clickEvent.clientX,
yPos: clickEvent.clientY,
});
};
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
if (!e.data) return;
2022-12-31 19:26:58 -08:00
handlePlayQueueAdd?.({
2022-12-31 18:03:26 -08:00
byData: [e.data],
play: playButtonBehavior,
});
};
return (
2023-01-01 13:58:05 -08:00
<>
2022-12-31 18:03:26 -08:00
<VirtualTable
// https://github.com/ag-grid/ag-grid/issues/5284
// Key is used to force remount of table when display, rowHeight, or server changes
key={`table-${page.display}-${page.table.rowHeight}-${server?.id}`}
ref={tableRef}
2023-01-01 13:58:05 -08:00
alwaysShowHorizontalScroll
2022-12-31 18:03:26 -08:00
animateRows
maintainColumnOrder
suppressCopyRowsToClipboard
suppressMoveWhenRowDragging
suppressPaginationPanel
suppressRowDrag
suppressScrollOnNewData
2023-01-01 13:58:05 -08:00
blockLoadDebounceMillis={200}
cacheBlockSize={500}
cacheOverflowSize={1}
2022-12-31 18:03:26 -08:00
columnDefs={columnDefs}
defaultColDef={defaultColumnDefs}
enableCellChangeFlash={false}
2023-01-01 13:58:05 -08:00
getRowId={(data) => data.data.id}
infiniteInitialRowCount={checkPlaylistList.data?.totalRecordCount || 100}
pagination={isPaginationEnabled}
paginationAutoPageSize={isPaginationEnabled}
paginationPageSize={pagination.itemsPerPage || 100}
rowBuffer={20}
rowHeight={page.table.rowHeight || 40}
rowModelType="infinite"
2022-12-31 18:03:26 -08:00
rowSelection="multiple"
onBodyScrollEnd={handleScroll}
onCellContextMenu={handleContextMenu}
onColumnMoved={handleColumnChange}
onColumnResized={debouncedColumnChange}
2023-01-01 13:58:05 -08:00
onGridReady={onGridReady}
2022-12-31 18:03:26 -08:00
onGridSizeChanged={handleGridSizeChange}
2023-01-01 13:58:05 -08:00
onPaginationChanged={onPaginationChanged}
2022-12-31 18:03:26 -08:00
onRowDoubleClicked={handleRowDoubleClick}
/>
2023-01-01 13:58:05 -08:00
<AnimatePresence
2022-12-31 18:03:26 -08:00
presenceAffectsLayout
initial={false}
mode="wait"
>
{page.display === ListDisplayType.TABLE_PAGINATED && (
<TablePagination
2023-01-01 13:58:05 -08:00
id={playlistId}
2022-12-31 18:03:26 -08:00
pagination={pagination}
2023-01-01 13:58:05 -08:00
setIdPagination={setPagination}
2022-12-31 18:03:26 -08:00
tableRef={tableRef}
/>
)}
2023-01-01 13:58:05 -08:00
</AnimatePresence>
</>
2022-12-31 18:03:26 -08:00
);
};