Update album artist list implementation

This commit is contained in:
jeffvli 2023-07-19 20:53:49 -07:00
parent 6cd27c3e88
commit 440cc04fbc
11 changed files with 187 additions and 309 deletions

View File

@ -1,15 +1,16 @@
import { MutableRefObject, useMemo } from 'react'; import type { RowDoubleClickedEvent } from '@ag-grid-community/core';
import type { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { useCurrentServer, useSongListStore } from '/@/renderer/store'; import { MutableRefObject } from 'react';
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { useListContext } from '../../../context/list-context';
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { LibraryItem, QueueSong, SongListQuery } from '/@/renderer/api/types';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem, QueueSong } from '/@/renderer/api/types';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid'; import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table'; import { VirtualTable } from '/@/renderer/components/virtual-table';
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles'; import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table';
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useCurrentServer } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
interface AlbumArtistSongListContentProps { interface AlbumArtistSongListContentProps {
data: QueueSong[]; data: QueueSong[];
@ -21,18 +22,11 @@ export const AlbumArtistDetailTopSongsListContent = ({
data, data,
}: AlbumArtistSongListContentProps) => { }: AlbumArtistSongListContentProps) => {
const server = useCurrentServer(); const server = useCurrentServer();
const page = useSongListStore(); const { id, pageKey } = useListContext();
const handlePlayQueueAdd = usePlayQueueAdd(); const handlePlayQueueAdd = usePlayQueueAdd();
const playButtonBehavior = usePlayButtonBehavior(); const playButtonBehavior = usePlayButtonBehavior();
const columnDefs: ColDef[] = useMemo(
() => getColumnDefs(page.table.columns),
[page.table.columns],
);
const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => { const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
if (!e.data) return; if (!e.data) return;
@ -49,35 +43,33 @@ export const AlbumArtistDetailTopSongsListContent = ({
}); });
}; };
const customFilters: Partial<SongListQuery> = {
...(id && { artistIds: [id] }),
};
const { rowClassRules } = useCurrentSongRowStyles({ tableRef }); const { rowClassRules } = useCurrentSongRowStyles({ tableRef });
const tableProps = useVirtualTable({
contextMenu: SONG_CONTEXT_MENU_ITEMS,
customFilters,
itemType: LibraryItem.SONG,
pageKey,
server,
tableRef,
});
return ( return (
<> <>
<VirtualGridAutoSizerContainer> <VirtualGridAutoSizerContainer>
<VirtualTable <VirtualTable
// https://github.com/ag-grid/ag-grid/issues/5284 key={`table-${tableProps.rowHeight}-${server?.id}`}
// 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} ref={tableRef}
alwaysShowHorizontalScroll {...tableProps}
animateRows
maintainColumnOrder
suppressCopyRowsToClipboard
suppressMoveWhenRowDragging
suppressPaginationPanel
suppressRowDrag
suppressScrollOnNewData
autoFitColumns={page.table.autoFit}
columnDefs={columnDefs}
enableCellChangeFlash={false}
getRowId={(data) => data.data.uniqueId} getRowId={(data) => data.data.uniqueId}
rowBuffer={20}
rowClassRules={rowClassRules} rowClassRules={rowClassRules}
rowData={data} rowData={data}
rowHeight={page.table.rowHeight || 40}
rowModelType="clientSide" rowModelType="clientSide"
rowSelection="multiple" rowSelection="multiple"
onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick} onRowDoubleClicked={handleRowDoubleClick}
/> />
</VirtualGridAutoSizerContainer> </VirtualGridAutoSizerContainer>

View File

@ -2,8 +2,9 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li
import { lazy, MutableRefObject, Suspense } from 'react'; import { lazy, MutableRefObject, Suspense } from 'react';
import { Spinner } from '/@/renderer/components'; import { Spinner } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { useAlbumArtistListStore } from '/@/renderer/store';
import { ListDisplayType } from '/@/renderer/types'; import { ListDisplayType } from '/@/renderer/types';
import { useListStoreByKey } from '../../../store/list.store';
import { useListContext } from '/@/renderer/context/list-context';
const AlbumArtistListGridView = lazy(() => const AlbumArtistListGridView = lazy(() =>
import('/@/renderer/features/artists/components/album-artist-list-grid-view').then( import('/@/renderer/features/artists/components/album-artist-list-grid-view').then(
@ -32,7 +33,8 @@ export const AlbumArtistListContent = ({
gridRef, gridRef,
tableRef, tableRef,
}: AlbumArtistListContentProps) => { }: AlbumArtistListContentProps) => {
const { display } = useAlbumArtistListStore(); const { pageKey } = useListContext();
const { display } = useListStoreByKey({ key: pageKey });
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER; const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER;
return ( return (

View File

@ -1,22 +1,24 @@
import { useQueryClient } from '@tanstack/react-query'; import { QueryKey, useQueryClient } from '@tanstack/react-query';
import { MutableRefObject, useCallback, useMemo } from 'react'; import { MutableRefObject, useCallback, useMemo } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { ListOnScrollProps } from 'react-window'; import { ListOnScrollProps } from 'react-window';
import { VirtualGridAutoSizerContainer } from '../../../components/virtual-grid/virtual-grid-wrapper'; import { VirtualGridAutoSizerContainer } from '../../../components/virtual-grid/virtual-grid-wrapper';
import { useListStoreByKey } from '../../../store/list.store';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { AlbumArtist, AlbumArtistListSort, LibraryItem } from '/@/renderer/api/types'; import {
AlbumArtist,
AlbumArtistListQuery,
AlbumArtistListResponse,
AlbumArtistListSort,
LibraryItem,
} from '/@/renderer/api/types';
import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components'; import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components';
import { VirtualInfiniteGrid, VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGrid, VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context'; import { useListContext } from '/@/renderer/context/list-context';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { import { useCurrentServer, useListStoreActions } from '/@/renderer/store';
useAlbumArtistListFilter,
useAlbumArtistListStore,
useCurrentServer,
useListStoreActions,
} from '/@/renderer/store';
import { CardRow, ListDisplayType } from '/@/renderer/types'; import { CardRow, ListDisplayType } from '/@/renderer/types';
interface AlbumArtistListGridViewProps { interface AlbumArtistListGridViewProps {
@ -29,18 +31,55 @@ export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListG
const server = useCurrentServer(); const server = useCurrentServer();
const handlePlayQueueAdd = usePlayQueueAdd(); const handlePlayQueueAdd = usePlayQueueAdd();
const { id, pageKey } = useAlbumArtistListContext(); const { pageKey } = useListContext();
const filter = useAlbumArtistListFilter({ id, key: pageKey }); const { grid, display, filter } = useListStoreByKey({ key: pageKey });
const { grid, display } = useAlbumArtistListStore();
const { setGrid } = useListStoreActions(); const { setGrid } = useListStoreActions();
const fetchInitialData = useCallback(() => {
const query: Omit<AlbumArtistListQuery, 'startIndex' | 'limit'> = {
...filter,
};
const queriesFromCache: [QueryKey, AlbumArtistListResponse][] = queryClient.getQueriesData({
exact: false,
fetchStatus: 'idle',
queryKey: queryKeys.albumArtists.list(server?.id || '', query),
stale: false,
});
const itemData = [];
for (const [, data] of queriesFromCache) {
const { items, startIndex } = data || {};
if (items && startIndex !== undefined) {
let itemIndex = 0;
for (
let rowIndex = startIndex;
rowIndex < startIndex + items.length;
rowIndex += 1
) {
itemData[rowIndex] = items[itemIndex];
itemIndex += 1;
}
}
}
return itemData;
}, [filter, queryClient, server?.id]);
const fetch = useCallback( const fetch = useCallback(
async ({ skip: startIndex, take: limit }: { skip: number; take: number }) => { async ({ skip: startIndex, take: limit }: { skip: number; take: number }) => {
const queryKey = queryKeys.albumArtists.list(server?.id || '', { const queryKey = queryKeys.albumArtists.list(
limit, server?.id || '',
startIndex, {
...filter, ...filter,
}); },
{
limit,
startIndex,
},
);
const albumArtistsRes = await queryClient.fetchQuery( const albumArtistsRes = await queryClient.fetchQuery(
queryKey, queryKey,
@ -114,6 +153,7 @@ export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListG
cardRows={cardRows} cardRows={cardRows}
display={display || ListDisplayType.CARD} display={display || ListDisplayType.CARD}
fetchFn={fetch} fetchFn={fetch}
fetchInitialData={fetchInitialData}
handlePlayQueueAdd={handlePlayQueueAdd} handlePlayQueueAdd={handlePlayQueueAdd}
height={height} height={height}
initialScrollOffset={grid?.scrollOffset || 0} initialScrollOffset={grid?.scrollOffset || 0}

View File

@ -5,7 +5,7 @@ import { useQueryClient } from '@tanstack/react-query';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react'; import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { RiFolder2Line, RiMoreFill, RiRefreshLine, RiSettings3Fill } from 'react-icons/ri'; import { RiFolder2Line, RiMoreFill, RiRefreshLine, RiSettings3Fill } from 'react-icons/ri';
import { useAlbumArtistListContext } from '../context/album-artist-list-context'; import { useListContext } from '../../../context/list-context';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/renderer/api/types'; import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
@ -16,10 +16,9 @@ import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared'
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { import {
AlbumArtistListFilter, AlbumArtistListFilter,
useAlbumArtistListFilter,
useAlbumArtistListStore,
useCurrentServer, useCurrentServer,
useListStoreActions, useListStoreActions,
useListStoreByKey,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { ListDisplayType, ServerType, TableColumn } from '/@/renderer/types'; import { ListDisplayType, ServerType, TableColumn } from '/@/renderer/types';
@ -65,11 +64,10 @@ export const AlbumArtistListHeaderFilters = ({
}: AlbumArtistListHeaderFiltersProps) => { }: AlbumArtistListHeaderFiltersProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const server = useCurrentServer(); const server = useCurrentServer();
const { pageKey } = useAlbumArtistListContext(); const { pageKey } = useListContext();
const { display, table, grid } = useAlbumArtistListStore(); const { display, table, grid, filter } = useListStoreByKey({ key: pageKey });
const { setFilter, setTable, setTablePagination, setDisplayType, setGrid } = const { setFilter, setTable, setTablePagination, setDisplayType, setGrid } =
useListStoreActions(); useListStoreActions();
const filter = useAlbumArtistListFilter({ key: pageKey });
const cq = useContainerQuery(); const cq = useContainerQuery();
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER; const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER;

View File

@ -1,27 +1,19 @@
import type { ChangeEvent, MutableRefObject } from 'react';
import { useCallback } from 'react';
import { IDatasource } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Flex, Group, Stack } from '@mantine/core'; import { Flex, Group, Stack } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { api } from '/@/renderer/api'; import type { ChangeEvent, MutableRefObject } from 'react';
import { queryKeys } from '/@/renderer/api/query-keys'; import { useListContext } from '../../../context/list-context';
import { useListStoreByKey } from '../../../store/list.store';
import { FilterBar } from '../../shared/components/filter-bar';
import { LibraryItem } from '/@/renderer/api/types';
import { PageHeader, SearchInput } from '/@/renderer/components'; import { PageHeader, SearchInput } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
import { LibraryHeaderBar } from '/@/renderer/features/shared'; import { LibraryHeaderBar } from '/@/renderer/features/shared';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { import { useListFilterRefresh } from '/@/renderer/hooks/use-list-filter-refresh';
AlbumArtistListFilter, import { AlbumArtistListFilter, useCurrentServer, useListStoreActions } from '/@/renderer/store';
useAlbumArtistListStore,
useCurrentServer,
useListStoreActions,
} from '/@/renderer/store';
import { ListDisplayType } from '/@/renderer/types'; import { ListDisplayType } from '/@/renderer/types';
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
import { FilterBar } from '../../shared/components/filter-bar';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { LibraryItem } from '/@/renderer/api/types';
interface AlbumArtistListHeaderProps { interface AlbumArtistListHeaderProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>; gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
@ -34,113 +26,31 @@ export const AlbumArtistListHeader = ({
gridRef, gridRef,
tableRef, tableRef,
}: AlbumArtistListHeaderProps) => { }: AlbumArtistListHeaderProps) => {
const queryClient = useQueryClient();
const server = useCurrentServer(); const server = useCurrentServer();
const { pageKey } = useAlbumArtistListContext(); const { pageKey } = useListContext();
const { display, filter } = useAlbumArtistListStore(); const { display, filter } = useListStoreByKey({ key: pageKey });
const { setFilter, setTablePagination } = useListStoreActions(); const { setFilter, setTablePagination } = useListStoreActions();
const cq = useContainerQuery(); const cq = useContainerQuery();
const fetch = useCallback( const { handleRefreshGrid, handleRefreshTable } = useListFilterRefresh({
async (startIndex: number, limit: number, filters: AlbumArtistListFilter) => { itemType: LibraryItem.ALBUM_ARTIST,
const queryKey = queryKeys.albumArtists.list(server?.id || '', { server,
limit, });
startIndex,
...filters,
});
const albums = await queryClient.fetchQuery(
queryKey,
async ({ signal }) =>
api.controller.getAlbumArtistList({
apiClientProps: {
server,
signal,
},
query: {
limit,
startIndex,
...filters,
},
}),
{ cacheTime: 1000 * 60 * 1 },
);
return albums;
},
[queryClient, server],
);
const handleFilterChange = useCallback(
async (filters: AlbumArtistListFilter) => {
if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
const queryKey = queryKeys.albumArtists.list(server?.id || '', {
limit,
startIndex,
...filters,
});
const albumArtistsRes = await queryClient.fetchQuery(
queryKey,
async ({ signal }) =>
api.controller.getAlbumArtistList({
apiClientProps: {
server,
signal,
},
query: {
limit,
startIndex,
...filters,
},
}),
{ cacheTime: 1000 * 60 * 1 },
);
params.successCallback(
albumArtistsRes?.items || [],
albumArtistsRes?.totalRecordCount || 0,
);
},
rowCount: undefined,
};
tableRef.current?.api.setDatasource(dataSource);
tableRef.current?.api.purgeInfiniteCache();
tableRef.current?.api.ensureIndexVisible(0, 'top');
if (display === ListDisplayType.TABLE_PAGINATED) {
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
}
} else {
gridRef.current?.scrollTo(0);
gridRef.current?.resetLoadMoreItemsCache();
// Refetching within the virtualized grid may be inconsistent due to it refetching
// using an outdated set of filters. To avoid this, we fetch using the updated filters
// and then set the grid's data here.
const data = await fetch(0, 200, filters);
if (!data?.items) return;
gridRef.current?.setItemData(data.items);
}
},
[display, tableRef, server, queryClient, setTablePagination, pageKey, gridRef, fetch],
);
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => { const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
const previousSearchTerm = filter.searchTerm;
const searchTerm = e.target.value === '' ? undefined : e.target.value; const searchTerm = e.target.value === '' ? undefined : e.target.value;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
data: { searchTerm }, data: { searchTerm },
itemType: LibraryItem.ALBUM_ARTIST, itemType: LibraryItem.ALBUM_ARTIST,
key: pageKey, key: pageKey,
}) as AlbumArtistListFilter; }) as AlbumArtistListFilter;
if (previousSearchTerm !== searchTerm) handleFilterChange(updatedFilters);
if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) {
handleRefreshTable(tableRef, updatedFilters);
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
} else {
handleRefreshGrid(gridRef, updatedFilters);
}
}, 500); }, 500);
return ( return (

View File

@ -1,22 +1,12 @@
import { MutableRefObject, useCallback } from 'react';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { AlbumArtistListQuery, AlbumArtistListResponse, LibraryItem } from '/@/renderer/api/types'; import { MutableRefObject } from 'react';
import { import { useListContext } from '../../../context/list-context';
AgGridFetchFn,
useVirtualTable,
} from '/@/renderer/components/virtual-table/hooks/use-virtual-table';
import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
import { ARTIST_CONTEXT_MENU_ITEMS } from '../../context-menu/context-menu-items'; import { ARTIST_CONTEXT_MENU_ITEMS } from '../../context-menu/context-menu-items';
import { import { LibraryItem } from '/@/renderer/api/types';
useAlbumArtistListFilter,
useAlbumArtistListStore,
useCurrentServer,
useListStoreActions,
} from '/@/renderer/store';
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid'; import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
import { VirtualTable } from '/@/renderer/components/virtual-table'; import { VirtualTable } from '/@/renderer/components/virtual-table';
import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table';
import { useCurrentServer } from '/@/renderer/store';
interface AlbumArtistListTableViewProps { interface AlbumArtistListTableViewProps {
itemCount?: number; itemCount?: number;
@ -28,53 +18,14 @@ export const AlbumArtistListTableView = ({
tableRef, tableRef,
}: AlbumArtistListTableViewProps) => { }: AlbumArtistListTableViewProps) => {
const server = useCurrentServer(); const server = useCurrentServer();
const { id, pageKey } = useAlbumArtistListContext(); const { pageKey } = useListContext();
const filter = useAlbumArtistListFilter({ id, key: pageKey });
const listProperties = useAlbumArtistListStore();
const { setTable, setTablePagination } = useListStoreActions();
const fetchFn: AgGridFetchFn< const tableProps = useVirtualTable({
AlbumArtistListResponse,
Omit<AlbumArtistListQuery, 'startIndex'>
> = useCallback(
async ({ filter, limit, startIndex }, signal) => {
const res = api.controller.getAlbumArtistList({
apiClientProps: {
server,
signal,
},
query: {
...filter,
limit,
sortBy: filter.sortBy,
sortOrder: filter.sortOrder,
startIndex,
},
});
return res;
},
[server],
);
const tableProps = useVirtualTable<
AlbumArtistListResponse,
Omit<AlbumArtistListQuery, 'startIndex'>
>({
contextMenu: ARTIST_CONTEXT_MENU_ITEMS, contextMenu: ARTIST_CONTEXT_MENU_ITEMS,
fetch: {
filter,
fn: fetchFn,
itemCount,
queryKey: queryKeys.albums.list,
server,
},
itemCount, itemCount,
itemType: LibraryItem.SONG, itemType: LibraryItem.ALBUM_ARTIST,
pageKey, pageKey,
properties: listProperties, server,
setTable,
setTablePagination,
tableRef, tableRef,
}); });
@ -83,7 +34,7 @@ export const AlbumArtistListTableView = ({
<VirtualTable <VirtualTable
// https://github.com/ag-grid/ag-grid/issues/5284 // https://github.com/ag-grid/ag-grid/issues/5284
// Key is used to force remount of table when display, rowHeight, or server changes // Key is used to force remount of table when display, rowHeight, or server changes
key={`table-${listProperties.display}-${listProperties.table.rowHeight}-${server?.id}`} key={`table-${tableProps.rowHeight}-${server?.id}`}
ref={tableRef} ref={tableRef}
{...tableProps} {...tableProps}
/> />

View File

@ -1,11 +0,0 @@
import { createContext, useContext } from 'react';
import { ListKey } from '/@/renderer/store';
export const AlbumArtistDetailSongListContext = createContext<{ id?: string; pageKey: ListKey }>({
pageKey: 'albumArtist',
});
export const useAlbumArtistDetailSongListContext = () => {
const ctxValue = useContext(AlbumArtistDetailSongListContext);
return ctxValue;
};

View File

@ -1,11 +0,0 @@
import { createContext, useContext } from 'react';
import { ListKey } from '/@/renderer/store';
export const AlbumArtistListContext = createContext<{ id?: string; pageKey: ListKey }>({
pageKey: 'albumArtist',
});
export const useAlbumArtistListContext = () => {
const ctxValue = useContext(AlbumArtistListContext);
return ctxValue;
};

View File

@ -6,12 +6,15 @@ import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artist
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query'; import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query'; import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
import { AnimatedPage } from '/@/renderer/features/shared'; import { AnimatedPage } from '/@/renderer/features/shared';
import { LibraryItem } from '../../../api/types';
import { useCurrentServer } from '../../../store/auth.store'; import { useCurrentServer } from '../../../store/auth.store';
import { ListContext } from '/@/renderer/context/list-context';
const AlbumArtistDetailTopSongsListRoute = () => { const AlbumArtistDetailTopSongsListRoute = () => {
const tableRef = useRef<AgGridReactType | null>(null); const tableRef = useRef<AgGridReactType | null>(null);
const { albumArtistId } = useParams() as { albumArtistId: string }; const { albumArtistId } = useParams() as { albumArtistId: string };
const server = useCurrentServer(); const server = useCurrentServer();
const pageKey = LibraryItem.SONG;
const detailQuery = useAlbumArtistDetail({ const detailQuery = useAlbumArtistDetail({
query: { id: albumArtistId }, query: { id: albumArtistId },
@ -30,15 +33,17 @@ const AlbumArtistDetailTopSongsListRoute = () => {
return ( return (
<AnimatedPage> <AnimatedPage>
<AlbumArtistDetailTopSongsListHeader <ListContext.Provider value={{ id: albumArtistId, pageKey }}>
data={topSongsQuery?.data?.items || []} <AlbumArtistDetailTopSongsListHeader
itemCount={itemCount} data={topSongsQuery?.data?.items || []}
title={detailQuery?.data?.name || 'Unknown'} itemCount={itemCount}
/> title={detailQuery?.data?.name || 'Unknown'}
<AlbumArtistDetailTopSongsListContent />
data={topSongsQuery?.data?.items || []} <AlbumArtistDetailTopSongsListContent
tableRef={tableRef} data={topSongsQuery?.data?.items || []}
/> tableRef={tableRef}
/>
</ListContext.Provider>
</AnimatedPage> </AnimatedPage>
); );
}; };

View File

@ -1,21 +1,22 @@
import { AlbumArtistListHeader } from '/@/renderer/features/artists/components/album-artist-list-header';
import { AnimatedPage } from '/@/renderer/features/shared';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { useRef } from 'react'; import { useRef } from 'react';
import { AlbumArtistListContent } from '/@/renderer/features/artists/components/album-artist-list-content';
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
import { generatePageKey, useAlbumArtistListFilter } from '/@/renderer/store';
import { AlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context';
import { useCurrentServer } from '../../../store/auth.store'; import { useCurrentServer } from '../../../store/auth.store';
import { useListFilterByKey } from '../../../store/list.store';
import { LibraryItem } from '/@/renderer/api/types';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { ListContext } from '/@/renderer/context/list-context';
import { AlbumArtistListContent } from '/@/renderer/features/artists/components/album-artist-list-content';
import { AlbumArtistListHeader } from '/@/renderer/features/artists/components/album-artist-list-header';
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
import { AnimatedPage } from '/@/renderer/features/shared';
const AlbumArtistListRoute = () => { const AlbumArtistListRoute = () => {
const gridRef = useRef<VirtualInfiniteGridRef | null>(null); const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
const tableRef = useRef<AgGridReactType | null>(null); const tableRef = useRef<AgGridReactType | null>(null);
const pageKey = generatePageKey('albumArtist', undefined); const pageKey = LibraryItem.ALBUM_ARTIST;
const server = useCurrentServer(); const server = useCurrentServer();
const albumArtistListFilter = useAlbumArtistListFilter({ id: undefined, key: pageKey }); const albumArtistListFilter = useListFilterByKey({ key: pageKey });
const itemCountCheck = useAlbumArtistList({ const itemCountCheck = useAlbumArtistList({
options: { options: {
@ -37,7 +38,7 @@ const AlbumArtistListRoute = () => {
return ( return (
<AnimatedPage> <AnimatedPage>
<AlbumArtistListContext.Provider value={{ id: undefined, pageKey }}> <ListContext.Provider value={{ id: undefined, pageKey }}>
<AlbumArtistListHeader <AlbumArtistListHeader
gridRef={gridRef} gridRef={gridRef}
itemCount={itemCount} itemCount={itemCount}
@ -48,7 +49,7 @@ const AlbumArtistListRoute = () => {
itemCount={itemCount} itemCount={itemCount}
tableRef={tableRef} tableRef={tableRef}
/> />
</AlbumArtistListContext.Provider> </ListContext.Provider>
</AnimatedPage> </AnimatedPage>
); );
}; };

View File

@ -55,6 +55,7 @@ export interface ListState {
item: { item: {
album: ListItemProps<AlbumListFilter>; album: ListItemProps<AlbumListFilter>;
albumArtist: ListItemProps<AlbumArtistListFilter>; albumArtist: ListItemProps<AlbumArtistListFilter>;
albumArtistSong: ListItemProps<SongListFilter>;
albumDetail: ListItemProps<any>; albumDetail: ListItemProps<any>;
playlist: ListItemProps<PlaylistListFilter>; playlist: ListItemProps<PlaylistListFilter>;
song: ListItemProps<SongListFilter>; song: ListItemProps<SongListFilter>;
@ -379,6 +380,43 @@ export const useListStore = create<ListSlice>()(
scrollOffset: 0, scrollOffset: 0,
}, },
}, },
albumArtistSong: {
display: ListDisplayType.TABLE,
filter: {
sortBy: SongListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC,
},
grid: { itemsPerRow: 5, scrollOffset: 0 },
table: {
autoFit: true,
columns: [
{
column: TableColumn.ROW_INDEX,
width: 50,
},
{
column: TableColumn.TITLE_COMBINED,
width: 500,
},
{
column: TableColumn.ALBUM,
width: 300,
},
{
column: TableColumn.DURATION,
width: 100,
},
],
pagination: {
currentPage: 1,
itemsPerPage: 100,
totalItems: 1,
totalPages: 1,
},
rowHeight: 60,
scrollOffset: 0,
},
},
albumDetail: { albumDetail: {
display: ListDisplayType.TABLE, display: ListDisplayType.TABLE,
filter: { filter: {
@ -450,28 +488,12 @@ export const useListStore = create<ListSlice>()(
column: TableColumn.TITLE_COMBINED, column: TableColumn.TITLE_COMBINED,
width: 500, width: 500,
}, },
{
column: TableColumn.DURATION,
width: 100,
},
{ {
column: TableColumn.ALBUM, column: TableColumn.ALBUM,
width: 300, width: 300,
}, },
{ {
column: TableColumn.ARTIST, column: TableColumn.DURATION,
width: 100,
},
{
column: TableColumn.YEAR,
width: 100,
},
{
column: TableColumn.DATE_ADDED,
width: 100,
},
{
column: TableColumn.PLAY_COUNT,
width: 100, width: 100,
}, },
], ],
@ -552,9 +574,6 @@ export const useAlbumListStore = (args?: { id?: string; key?: string }) =>
}; };
}, shallow); }, shallow);
export const useAlbumArtistListStore = () =>
useListStore((state) => state.item.albumArtist, shallow);
export const useSongListStore = (args?: { id?: string; key?: string }) => export const useSongListStore = (args?: { id?: string; key?: string }) =>
useListStore((state) => { useListStore((state) => {
const detail = args?.key ? state.detail[args.key] : undefined; const detail = args?.key ? state.detail[args.key] : undefined;
@ -597,15 +616,6 @@ export const usePlaylistListStore = (args?: { key?: string }) =>
}; };
}, shallow); }, shallow);
export const useSongListFilter = (args: { id?: string; key?: string }) =>
useListStore((state) => {
return state._actions.getFilter({
id: args.id,
itemType: LibraryItem.SONG,
key: args.key,
}) as SongListFilter;
}, shallow);
export const useAlbumListFilter = (args: { id?: string; key?: string }) => export const useAlbumListFilter = (args: { id?: string; key?: string }) =>
useListStore((state) => { useListStore((state) => {
return state._actions.getFilter({ return state._actions.getFilter({
@ -615,13 +625,4 @@ export const useAlbumListFilter = (args: { id?: string; key?: string }) =>
}) as AlbumListFilter; }) as AlbumListFilter;
}, shallow); }, shallow);
export const useAlbumArtistListFilter = (args: { id?: string; key?: string }) =>
useListStore((state) => {
return state._actions.getFilter({
id: args.id,
itemType: LibraryItem.ALBUM_ARTIST,
key: args.key,
}) as AlbumArtistListFilter;
}, shallow);
export const useListDetail = (key: string) => useListStore((state) => state.detail[key], shallow); export const useListDetail = (key: string) => useListStore((state) => state.detail[key], shallow);