feishin/src/renderer/features/albums/components/album-list-content.tsx

384 lines
12 KiB
TypeScript
Raw Normal View History

import {
ALBUM_CARD_ROWS,
2022-12-28 01:44:49 -08:00
getColumnDefs,
TablePagination,
VirtualGridAutoSizerContainer,
VirtualInfiniteGrid,
2022-12-24 20:20:17 -08:00
VirtualInfiniteGridRef,
2022-12-28 01:44:49 -08:00
VirtualTable,
} from '/@/renderer/components';
import { AppRoute } from '/@/renderer/router/routes';
2023-01-05 21:59:07 -08:00
import { ListDisplayType, CardRow } from '/@/renderer/types';
import AutoSizer from 'react-virtualized-auto-sizer';
2023-03-09 18:09:59 -08:00
import { MutableRefObject, useCallback, useMemo } from 'react';
import { ListOnScrollProps } from 'react-window';
import { api } from '/@/renderer/api';
import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys';
2023-01-15 16:22:07 -08:00
import { Album, AlbumListQuery, AlbumListSort, LibraryItem } from '/@/renderer/api/types';
import { useQueryClient } from '@tanstack/react-query';
2022-12-28 01:44:49 -08:00
import {
useCurrentServer,
useAlbumListStore,
useAlbumListItemData,
2023-03-05 18:28:26 -08:00
useListStoreActions,
useAlbumListFilter,
2022-12-28 01:44:49 -08:00
} from '/@/renderer/store';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import {
BodyScrollEvent,
ColDef,
GridReadyEvent,
IDatasource,
PaginationChangedEvent,
RowDoubleClickedEvent,
2022-12-28 01:44:49 -08:00
} from '@ag-grid-community/core';
import { AnimatePresence } from 'framer-motion';
import debounce from 'lodash/debounce';
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
import { ALBUM_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { generatePath, useNavigate } from 'react-router';
2022-12-31 19:26:58 -08:00
import { usePlayQueueAdd } from '/@/renderer/features/player';
2023-01-08 00:52:53 -08:00
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
2023-03-05 18:28:26 -08:00
import { useAlbumListContext } from '/@/renderer/features/albums/context/album-list-context';
2022-12-24 20:20:17 -08:00
interface AlbumListContentProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
itemCount?: number;
2022-12-28 01:44:49 -08:00
tableRef: MutableRefObject<AgGridReactType | null>;
2022-12-24 20:20:17 -08:00
}
2023-03-09 18:09:59 -08:00
export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListContentProps) => {
const queryClient = useQueryClient();
const navigate = useNavigate();
const server = useCurrentServer();
2022-12-31 19:26:58 -08:00
const handlePlayQueueAdd = usePlayQueueAdd();
const { itemData, setItemData } = useAlbumListItemData();
2023-03-05 18:28:26 -08:00
const { id, pageKey } = useAlbumListContext();
const filter = useAlbumListFilter({ id, key: pageKey });
const { setTable, setTablePagination, setGrid } = useListStoreActions();
2023-03-09 18:09:59 -08:00
const { table, grid, display } = useAlbumListStore({ id, key: pageKey });
2023-03-05 18:28:26 -08:00
const isPaginationEnabled = display === ListDisplayType.TABLE_PAGINATED;
2022-12-28 01:44:49 -08:00
2023-03-05 18:28:26 -08:00
const columnDefs: ColDef[] = useMemo(() => getColumnDefs(table.columns), [table.columns]);
2022-12-28 01:44:49 -08:00
const onTableReady = useCallback(
(params: GridReadyEvent) => {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
2023-01-15 16:22:07 -08:00
const query: AlbumListQuery = {
2022-12-28 01:44:49 -08:00
limit,
startIndex,
2023-03-05 18:28:26 -08:00
...filter,
2023-01-15 16:22:07 -08:00
jfParams: {
2023-03-05 18:28:26 -08:00
...filter.jfParams,
2023-01-15 16:22:07 -08:00
},
ndParams: {
2023-03-05 18:28:26 -08:00
...filter.ndParams,
2023-01-15 16:22:07 -08:00
},
};
const queryKey = queryKeys.albums.list(server?.id || '', query);
2022-12-28 01:44:49 -08:00
2023-01-06 03:32:35 -08:00
const albumsRes = await queryClient.fetchQuery(
queryKey,
async ({ signal }) =>
api.controller.getAlbumList({
2023-01-15 16:22:07 -08:00
query,
2023-01-06 03:32:35 -08:00
server,
signal,
}),
{ cacheTime: 1000 * 60 * 1 },
2022-12-28 01:44:49 -08:00
);
const albums = api.normalize.albumList(albumsRes, server);
params.successCallback(albums?.items || [], albumsRes?.totalRecordCount || 0);
2022-12-28 01:44:49 -08:00
},
rowCount: undefined,
};
params.api.setDatasource(dataSource);
},
2023-03-09 18:09:59 -08:00
[filter, queryClient, server],
2022-12-28 01:44:49 -08:00
);
const onTablePaginationChanged = useCallback(
(event: PaginationChangedEvent) => {
if (!isPaginationEnabled || !event.api) return;
try {
// Scroll to top of page on pagination change
2023-03-05 18:28:26 -08:00
const currentPageStartIndex = table.pagination.currentPage * table.pagination.itemsPerPage;
event.api?.ensureIndexVisible(currentPageStartIndex, 'top');
} catch (err) {
console.log(err);
}
2022-12-28 01:44:49 -08:00
2023-03-05 18:28:26 -08:00
setTablePagination({
data: {
itemsPerPage: event.api.paginationGetPageSize(),
totalItems: event.api.paginationGetRowCount(),
totalPages: event.api.paginationGetTotalPages() + 1,
},
key: pageKey,
2022-12-28 01:44:49 -08:00
});
},
2023-03-05 18:28:26 -08:00
[
isPaginationEnabled,
setTablePagination,
pageKey,
table.pagination.currentPage,
table.pagination.itemsPerPage,
],
2022-12-28 01:44:49 -08:00
);
const handleTableColumnChange = useCallback(() => {
const { columnApi } = tableRef?.current || {};
const columnsOrder = columnApi?.getAllGridColumns();
if (!columnsOrder) return;
2023-03-05 18:28:26 -08:00
const columnsInSettings = table.columns;
2022-12-28 01:44:49 -08:00
const updatedColumns = [];
for (const column of columnsOrder) {
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
if (columnInSettings) {
updatedColumns.push({
...columnInSettings,
2023-03-05 18:28:26 -08:00
...(!table.autoFit && {
2022-12-28 01:44:49 -08:00
width: column.getColDef().width,
}),
});
}
}
2023-03-05 18:28:26 -08:00
setTable({ data: { columns: updatedColumns }, key: pageKey });
}, [tableRef, table.columns, table.autoFit, setTable, pageKey]);
2022-12-28 01:44:49 -08:00
const debouncedTableColumnChange = debounce(handleTableColumnChange, 200);
const handleTableScroll = (e: BodyScrollEvent) => {
2023-03-05 18:28:26 -08:00
const scrollOffset = Number((e.top / table.rowHeight).toFixed(0));
setTable({ data: { scrollOffset }, key: pageKey });
2022-12-28 01:44:49 -08:00
};
const fetch = useCallback(
async ({ skip, take }: { skip: number; take: number }) => {
2023-01-15 16:22:07 -08:00
const query: AlbumListQuery = {
limit: take,
startIndex: skip,
2023-03-05 18:28:26 -08:00
...filter,
2023-01-15 16:22:07 -08:00
jfParams: {
2023-03-05 18:28:26 -08:00
...filter.jfParams,
2023-01-15 16:22:07 -08:00
},
ndParams: {
2023-03-05 18:28:26 -08:00
...filter.ndParams,
2023-01-15 16:22:07 -08:00
},
};
const queryKey = queryKeys.albums.list(server?.id || '', query);
const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
controller.getAlbumList({
2023-01-15 16:22:07 -08:00
query,
server,
signal,
}),
);
return api.normalize.albumList(albums, server);
},
2023-03-09 18:09:59 -08:00
[filter, queryClient, server],
);
const handleGridScroll = useCallback(
(e: ListOnScrollProps) => {
2023-03-05 18:28:26 -08:00
setGrid({ data: { scrollOffset: e.scrollOffset }, key: pageKey });
},
2023-03-09 18:09:59 -08:00
[pageKey, setGrid],
);
const cardRows = useMemo(() => {
const rows: CardRow<Album>[] = [ALBUM_CARD_ROWS.name];
2023-03-05 18:28:26 -08:00
switch (filter.sortBy) {
case AlbumListSort.ALBUM_ARTIST:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.releaseYear);
break;
case AlbumListSort.ARTIST:
rows.push(ALBUM_CARD_ROWS.artists);
rows.push(ALBUM_CARD_ROWS.releaseYear);
break;
case AlbumListSort.COMMUNITY_RATING:
rows.push(ALBUM_CARD_ROWS.albumArtists);
break;
case AlbumListSort.DURATION:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.duration);
break;
case AlbumListSort.FAVORITED:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.releaseYear);
break;
case AlbumListSort.NAME:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.releaseYear);
break;
case AlbumListSort.PLAY_COUNT:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.playCount);
break;
case AlbumListSort.RANDOM:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.releaseYear);
break;
case AlbumListSort.RATING:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.rating);
break;
case AlbumListSort.RECENTLY_ADDED:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.createdAt);
break;
case AlbumListSort.RECENTLY_PLAYED:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.lastPlayedAt);
break;
case AlbumListSort.SONG_COUNT:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.songCount);
break;
case AlbumListSort.YEAR:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.releaseYear);
break;
2022-12-25 02:33:14 -08:00
case AlbumListSort.RELEASE_DATE:
rows.push(ALBUM_CARD_ROWS.albumArtists);
rows.push(ALBUM_CARD_ROWS.releaseDate);
}
return rows;
2023-03-05 18:28:26 -08:00
}, [filter.sortBy]);
const handleContextMenu = useHandleTableContextMenu(LibraryItem.ALBUM, ALBUM_CONTEXT_MENU_ITEMS);
const handleRowDoubleClick = (e: RowDoubleClickedEvent) => {
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
};
2023-01-08 00:52:53 -08:00
const createFavoriteMutation = useCreateFavorite();
const deleteFavoriteMutation = useDeleteFavorite();
const handleFavorite = (options: {
id: string[];
isFavorite: boolean;
itemType: LibraryItem;
}) => {
const { id, itemType, isFavorite } = options;
if (isFavorite) {
deleteFavoriteMutation.mutate({
query: {
id,
type: itemType,
},
});
} else {
createFavoriteMutation.mutate({
query: {
id,
type: itemType,
},
});
}
};
return (
2022-12-28 01:44:49 -08:00
<>
<VirtualGridAutoSizerContainer>
2023-03-05 18:28:26 -08:00
{display === ListDisplayType.CARD || display === ListDisplayType.POSTER ? (
2022-12-28 01:44:49 -08:00
<AutoSizer>
{({ height, width }) => (
<>
<VirtualInfiniteGrid
2023-03-05 18:28:26 -08:00
key={`album-list-${server?.id}-${display}`}
ref={gridRef}
cardRows={cardRows}
2023-03-05 18:28:26 -08:00
display={display || ListDisplayType.CARD}
fetchFn={fetch}
2023-01-08 00:52:53 -08:00
handleFavorite={handleFavorite}
handlePlayQueueAdd={handlePlayQueueAdd}
height={height}
2023-03-09 18:09:59 -08:00
initialScrollOffset={grid?.scrollOffset || 0}
itemCount={itemCount || 0}
2023-03-09 18:09:59 -08:00
itemData={itemData}
itemGap={20}
2023-03-05 18:28:26 -08:00
itemSize={150 + (grid?.size || 0)}
itemType={LibraryItem.ALBUM}
loading={itemCount === undefined || itemCount === null}
minimumBatchSize={40}
route={{
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
}}
2023-03-09 18:09:59 -08:00
setItemData={setItemData}
width={width}
onScroll={handleGridScroll}
/>
</>
2022-12-28 01:44:49 -08:00
)}
</AutoSizer>
) : (
<VirtualTable
// https://github.com/ag-grid/ag-grid/issues/5284
// Key is used to force remount of table when display, rowHeight, or server changes
2023-03-05 18:28:26 -08:00
key={`table-${display}-${table.rowHeight}-${server?.id}`}
2022-12-28 01:44:49 -08:00
ref={tableRef}
alwaysShowHorizontalScroll
2023-01-15 16:22:07 -08:00
suppressRowDrag
2023-03-05 18:28:26 -08:00
autoFitColumns={table.autoFit}
2022-12-28 01:44:49 -08:00
blockLoadDebounceMillis={200}
columnDefs={columnDefs}
getRowId={(data) => data.data.id}
infiniteInitialRowCount={itemCount || 100}
2022-12-28 01:44:49 -08:00
pagination={isPaginationEnabled}
paginationAutoPageSize={isPaginationEnabled}
2023-03-05 18:28:26 -08:00
paginationPageSize={table.pagination.itemsPerPage || 100}
2022-12-28 01:44:49 -08:00
rowBuffer={20}
2023-03-05 18:28:26 -08:00
rowHeight={table.rowHeight || 40}
2022-12-28 01:44:49 -08:00
rowModelType="infinite"
onBodyScrollEnd={handleTableScroll}
onCellContextMenu={handleContextMenu}
2022-12-28 01:44:49 -08:00
onColumnMoved={handleTableColumnChange}
onColumnResized={debouncedTableColumnChange}
onGridReady={onTableReady}
onPaginationChanged={onTablePaginationChanged}
onRowDoubleClicked={handleRowDoubleClick}
/>
)}
2022-12-28 01:44:49 -08:00
</VirtualGridAutoSizerContainer>
{isPaginationEnabled && (
<AnimatePresence
presenceAffectsLayout
initial={false}
mode="wait"
>
2023-03-05 18:28:26 -08:00
{display === ListDisplayType.TABLE_PAGINATED && (
2022-12-28 01:44:49 -08:00
<TablePagination
2023-03-05 18:28:26 -08:00
pageKey={pageKey}
pagination={table.pagination}
setPagination={setTablePagination}
2022-12-28 01:44:49 -08:00
tableRef={tableRef}
/>
)}
</AnimatePresence>
)}
</>
);
};