feishin/src/renderer/features/songs/components/song-list-content.tsx

222 lines
7.2 KiB
TypeScript
Raw Normal View History

2022-12-27 13:52:50 -08:00
import { MutableRefObject, useCallback, useMemo } from 'react';
import type {
BodyScrollEvent,
ColDef,
GridReadyEvent,
IDatasource,
PaginationChangedEvent,
RowDoubleClickedEvent,
2022-12-27 13:52:50 -08:00
} from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Stack } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
getColumnDefs,
TablePagination,
VirtualGridAutoSizerContainer,
VirtualTable,
} from '/@/renderer/components';
import {
2023-01-15 20:39:43 -08:00
SongListFilter,
2022-12-27 13:52:50 -08:00
useCurrentServer,
useSetSongTable,
useSetSongTablePagination,
useSongListStore,
useSongTablePagination,
} from '/@/renderer/store';
2023-01-05 21:59:07 -08:00
import { ListDisplayType } from '/@/renderer/types';
2022-12-27 13:52:50 -08:00
import { AnimatePresence } from 'framer-motion';
import debounce from 'lodash/debounce';
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
2022-12-28 15:32:50 -08:00
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
2023-01-15 20:39:43 -08:00
import { LibraryItem, QueueSong, SongListQuery } from '/@/renderer/api/types';
2022-12-31 19:26:58 -08:00
import { usePlayQueueAdd } from '/@/renderer/features/player';
2022-12-27 13:52:50 -08:00
interface SongListContentProps {
2023-01-15 20:39:43 -08:00
customFilters?: Partial<SongListFilter>;
itemCount?: number;
2022-12-27 13:52:50 -08:00
tableRef: MutableRefObject<AgGridReactType | null>;
}
2023-01-15 20:39:43 -08:00
export const SongListContent = ({ customFilters, itemCount, tableRef }: SongListContentProps) => {
2022-12-27 13:52:50 -08:00
const queryClient = useQueryClient();
const server = useCurrentServer();
const page = useSongListStore();
const pagination = useSongTablePagination();
const setPagination = useSetSongTablePagination();
const setTable = useSetSongTable();
2022-12-31 19:26:58 -08:00
const handlePlayQueueAdd = usePlayQueueAdd();
const playButtonBehavior = usePlayButtonBehavior();
2022-12-27 13:52:50 -08:00
const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED;
2022-12-28 01:23:54 -08:00
const columnDefs: ColDef[] = useMemo(
() => getColumnDefs(page.table.columns),
[page.table.columns],
);
2022-12-27 13:52:50 -08:00
const onGridReady = useCallback(
(params: GridReadyEvent) => {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
2023-01-15 20:39:43 -08:00
const query: SongListQuery = {
2022-12-27 13:52:50 -08:00
limit,
startIndex,
...page.filter,
2023-01-15 20:39:43 -08:00
...customFilters,
};
const queryKey = queryKeys.songs.list(server?.id || '', query);
2022-12-27 13:52:50 -08:00
const songsRes = await queryClient.fetchQuery(
queryKey,
async ({ signal }) =>
api.controller.getSongList({
2023-01-15 20:39:43 -08:00
query,
server,
signal,
}),
{ cacheTime: 1000 * 60 * 1 },
2022-12-27 13:52:50 -08:00
);
const songs = api.normalize.songList(songsRes, server);
params.successCallback(songs?.items || [], songsRes?.totalRecordCount);
},
rowCount: undefined,
};
params.api.setDatasource(dataSource);
if (!customFilters) {
params.api.ensureIndexVisible(page.table.scrollOffset, 'top');
}
2022-12-27 13:52:50 -08:00
},
2023-01-15 20:39:43 -08:00
[customFilters, page.filter, page.table.scrollOffset, queryClient, server],
2022-12-27 13:52:50 -08:00
);
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({
itemsPerPage: event.api.paginationGetPageSize(),
totalItems: event.api.paginationGetRowCount(),
totalPages: event.api.paginationGetTotalPages() + 1,
});
},
[isPaginationEnabled, pagination.currentPage, pagination.itemsPerPage, setPagination],
);
const handleGridSizeChange = () => {
if (page.table.autoFit) {
tableRef?.current?.api.sizeColumnsToFit();
}
};
2022-12-28 01:23:54 -08:00
const handleColumnChange = useCallback(() => {
2022-12-27 13:52:50 -08:00
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 && {
2022-12-28 01:23:54 -08:00
width: column.getActualWidth(),
2022-12-27 13:52:50 -08:00
}),
});
}
}
setTable({ columns: updatedColumns });
}, [page.table.autoFit, page.table.columns, setTable, tableRef]);
2022-12-28 01:23:54 -08:00
const debouncedColumnChange = debounce(handleColumnChange, 200);
const handleScroll = (e: BodyScrollEvent) => {
if (customFilters) return;
2022-12-27 13:52:50 -08:00
const scrollOffset = Number((e.top / page.table.rowHeight).toFixed(0));
setTable({ scrollOffset });
2022-12-28 01:23:54 -08:00
};
2022-12-27 13:52:50 -08:00
const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
2022-12-28 15:32:50 -08:00
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
if (!e.data) return;
2022-12-31 19:26:58 -08:00
handlePlayQueueAdd?.({
byData: [e.data],
play: playButtonBehavior,
});
};
2022-12-27 13:52:50 -08:00
return (
<Stack
h="100%"
spacing={0}
>
<VirtualGridAutoSizerContainer>
<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}
alwaysShowHorizontalScroll
suppressRowDrag
2023-01-15 16:34:30 -08:00
autoFitColumns={page.table.autoFit}
2022-12-27 13:52:50 -08:00
blockLoadDebounceMillis={200}
cacheBlockSize={500}
cacheOverflowSize={1}
columnDefs={columnDefs}
enableCellChangeFlash={false}
2022-12-28 01:23:54 -08:00
getRowId={(data) => data.data.id}
infiniteInitialRowCount={itemCount || 100}
2022-12-27 13:52:50 -08:00
pagination={isPaginationEnabled}
paginationAutoPageSize={isPaginationEnabled}
2022-12-28 01:23:54 -08:00
paginationPageSize={page.table.pagination.itemsPerPage || 100}
2022-12-27 13:52:50 -08:00
rowBuffer={20}
rowHeight={page.table.rowHeight || 40}
rowModelType="infinite"
rowSelection="multiple"
onBodyScrollEnd={handleScroll}
2022-12-28 15:32:50 -08:00
onCellContextMenu={handleContextMenu}
2022-12-28 01:23:54 -08:00
onColumnMoved={handleColumnChange}
onColumnResized={debouncedColumnChange}
2022-12-27 13:52:50 -08:00
onGridReady={onGridReady}
onGridSizeChanged={handleGridSizeChange}
onPaginationChanged={onPaginationChanged}
onRowDoubleClicked={handleRowDoubleClick}
2022-12-27 13:52:50 -08:00
/>
</VirtualGridAutoSizerContainer>
<AnimatePresence
presenceAffectsLayout
2022-12-28 01:23:54 -08:00
initial={false}
2022-12-27 13:52:50 -08:00
mode="wait"
>
{page.display === ListDisplayType.TABLE_PAGINATED && (
<TablePagination
pagination={pagination}
setPagination={setPagination}
tableRef={tableRef}
/>
)}
</AnimatePresence>
</Stack>
);
};