From 5614ad54f2b60145f31b3f14d57b970d667bbd15 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 15 Jan 2023 16:22:07 -0800 Subject: [PATCH] Add view artist discography --- .../albums/components/album-list-content.tsx | 76 ++++++---- .../albums/components/album-list-header.tsx | 131 ++++++++++++------ .../components/jellyfin-album-filters.tsx | 128 ++++++++++------- .../components/navidrome-album-filters.tsx | 98 +++++++++---- .../albums/routes/album-list-route.tsx | 36 ++++- .../album-artist-detail-content.tsx | 18 +-- src/renderer/router/app-router.tsx | 6 +- 7 files changed, 338 insertions(+), 155 deletions(-) diff --git a/src/renderer/features/albums/components/album-list-content.tsx b/src/renderer/features/albums/components/album-list-content.tsx index ec1be5ab..488c1907 100644 --- a/src/renderer/features/albums/components/album-list-content.tsx +++ b/src/renderer/features/albums/components/album-list-content.tsx @@ -10,12 +10,12 @@ import { import { AppRoute } from '/@/renderer/router/routes'; import { ListDisplayType, CardRow } from '/@/renderer/types'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { MutableRefObject, useCallback, useMemo } from 'react'; +import { MutableRefObject, useCallback, useMemo, useState } 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'; -import { Album, AlbumListSort, LibraryItem } from '/@/renderer/api/types'; +import { Album, AlbumListQuery, AlbumListSort, LibraryItem } from '/@/renderer/api/types'; import { useQueryClient } from '@tanstack/react-query'; import { useCurrentServer, @@ -25,6 +25,7 @@ import { useSetAlbumTable, useSetAlbumTablePagination, useAlbumListItemData, + AlbumListFilter, } from '/@/renderer/store'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { @@ -44,12 +45,18 @@ import { usePlayQueueAdd } from '/@/renderer/features/player'; import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared'; interface AlbumListContentProps { + customFilters?: Partial; gridRef: MutableRefObject; itemCount?: number; tableRef: MutableRefObject; } -export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListContentProps) => { +export const AlbumListContent = ({ + customFilters, + itemCount, + gridRef, + tableRef, +}: AlbumListContentProps) => { const queryClient = useQueryClient(); const navigate = useNavigate(); const server = useCurrentServer(); @@ -58,6 +65,7 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont const handlePlayQueueAdd = usePlayQueueAdd(); const { itemData, setItemData } = useAlbumListItemData(); + const [localItemData, setLocalItemData] = useState([]); const pagination = useAlbumTablePagination(); const setPagination = useSetAlbumTablePagination(); @@ -77,21 +85,28 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont const limit = params.endRow - params.startRow; const startIndex = params.startRow; - const queryKey = queryKeys.albums.list(server?.id || '', { + const query: AlbumListQuery = { limit, startIndex, ...page.filter, - }); + ...customFilters, + jfParams: { + ...page.filter.jfParams, + ...customFilters?.jfParams, + }, + ndParams: { + ...page.filter.ndParams, + ...customFilters?.ndParams, + }, + }; + + const queryKey = queryKeys.albums.list(server?.id || '', query); const albumsRes = await queryClient.fetchQuery( queryKey, async ({ signal }) => api.controller.getAlbumList({ - query: { - limit, - startIndex, - ...page.filter, - }, + query, server, signal, }), @@ -104,9 +119,12 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont rowCount: undefined, }; params.api.setDatasource(dataSource); - params.api.ensureIndexVisible(page.table.scrollOffset || 0, 'top'); + + if (!customFilters) { + params.api.ensureIndexVisible(page.table.scrollOffset || 0, 'top'); + } }, - [page.filter, page.table.scrollOffset, queryClient, server], + [customFilters, page.filter, page.table.scrollOffset, queryClient, server], ); const onTablePaginationChanged = useCallback( @@ -153,25 +171,33 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont const debouncedTableColumnChange = debounce(handleTableColumnChange, 200); const handleTableScroll = (e: BodyScrollEvent) => { + if (customFilters) return; const scrollOffset = Number((e.top / page.table.rowHeight).toFixed(0)); setTable({ scrollOffset }); }; const fetch = useCallback( async ({ skip, take }: { skip: number; take: number }) => { - const queryKey = queryKeys.albums.list(server?.id || '', { + const query: AlbumListQuery = { limit: take, startIndex: skip, ...page.filter, - }); + ...customFilters, + jfParams: { + ...page.filter.jfParams, + ...customFilters?.jfParams, + }, + ndParams: { + ...page.filter.ndParams, + ...customFilters?.ndParams, + }, + }; + + const queryKey = queryKeys.albums.list(server?.id || '', query); const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) => controller.getAlbumList({ - query: { - limit: take, - startIndex: skip, - ...page.filter, - }, + query, server, signal, }), @@ -179,11 +205,12 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont return api.normalize.albumList(albums, server); }, - [page.filter, queryClient, server], + [customFilters, page.filter, queryClient, server], ); const handleGridScroll = useCallback( (e: ListOnScrollProps) => { + if (customFilters) return; setPage({ list: { ...page, @@ -194,7 +221,7 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont }, }); }, - [page, setPage], + [customFilters, page, setPage], ); const cardRows = useMemo(() => { @@ -308,9 +335,9 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont handleFavorite={handleFavorite} handlePlayQueueAdd={handlePlayQueueAdd} height={height} - initialScrollOffset={page?.grid.scrollOffset || 0} + initialScrollOffset={customFilters ? 0 : page?.grid.scrollOffset || 0} itemCount={itemCount || 0} - itemData={itemData} + itemData={customFilters ? localItemData : itemData} itemGap={20} itemSize={150 + page.grid?.size} itemType={LibraryItem.ALBUM} @@ -320,7 +347,7 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont route: AppRoute.LIBRARY_ALBUMS_DETAIL, slugs: [{ idProperty: 'id', slugProperty: 'albumId' }], }} - setItemData={setItemData} + setItemData={customFilters ? setLocalItemData : setItemData} width={width} onScroll={handleGridScroll} /> @@ -334,6 +361,7 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont key={`table-${page.display}-${page.table.rowHeight}-${server?.id}`} ref={tableRef} alwaysShowHorizontalScroll + suppressRowDrag autoFitColumns={page.table.autoFit} blockLoadDebounceMillis={200} columnDefs={columnDefs} diff --git a/src/renderer/features/albums/components/album-list-header.tsx b/src/renderer/features/albums/components/album-list-header.tsx index 28710be5..6921728e 100644 --- a/src/renderer/features/albums/components/album-list-header.tsx +++ b/src/renderer/features/albums/components/album-list-header.tsx @@ -3,6 +3,7 @@ import { useCallback } from 'react'; import { IDatasource } from '@ag-grid-community/core'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { Flex, Group, Stack } from '@mantine/core'; +import { openModal } from '@mantine/modals'; import { useQueryClient } from '@tanstack/react-query'; import debounce from 'lodash/debounce'; import { @@ -17,7 +18,13 @@ import styled from 'styled-components'; import { api } from '/@/renderer/api'; import { controller } from '/@/renderer/api/controller'; import { queryKeys } from '/@/renderer/api/query-keys'; -import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/renderer/api/types'; +import { + AlbumListQuery, + AlbumListSort, + LibraryItem, + ServerType, + SortOrder, +} from '/@/renderer/api/types'; import { ALBUM_TABLE_COLUMNS, Badge, @@ -25,7 +32,6 @@ import { DropdownMenu, MultiSelect, PageHeader, - Popover, SearchInput, Slider, SpinnerIcon, @@ -92,12 +98,20 @@ const HeaderItems = styled.div` `; interface AlbumListHeaderProps { + customFilters?: Partial; gridRef: MutableRefObject; itemCount?: number; tableRef: MutableRefObject; + title?: string; } -export const AlbumListHeader = ({ itemCount, gridRef, tableRef }: AlbumListHeaderProps) => { +export const AlbumListHeader = ({ + itemCount, + gridRef, + tableRef, + title, + customFilters, +}: AlbumListHeaderProps) => { const queryClient = useQueryClient(); const server = useCurrentServer(); const setPage = useSetAlbumStore(); @@ -131,21 +145,28 @@ export const AlbumListHeader = ({ itemCount, gridRef, tableRef }: AlbumListHeade const fetch = useCallback( async (skip: number, take: number, filters: AlbumListFilter) => { - const queryKey = queryKeys.albums.list(server?.id || '', { + const query: AlbumListQuery = { limit: take, startIndex: skip, ...filters, - }); + jfParams: { + ...filters.jfParams, + ...customFilters?.jfParams, + }, + ndParams: { + ...filters.ndParams, + ...customFilters?.ndParams, + }, + ...customFilters, + }; + + const queryKey = queryKeys.albums.list(server?.id || '', query); const albums = await queryClient.fetchQuery( queryKey, async ({ signal }) => controller.getAlbumList({ - query: { - limit: take, - startIndex: skip, - ...filters, - }, + query, server, signal, }), @@ -154,7 +175,7 @@ export const AlbumListHeader = ({ itemCount, gridRef, tableRef }: AlbumListHeade return api.normalize.albumList(albums, server); }, - [queryClient, server], + [customFilters, queryClient, server], ); const handleFilterChange = useCallback( @@ -168,21 +189,28 @@ export const AlbumListHeader = ({ itemCount, gridRef, tableRef }: AlbumListHeade const limit = params.endRow - params.startRow; const startIndex = params.startRow; - const queryKey = queryKeys.albums.list(server?.id || '', { + const query: AlbumListQuery = { limit, startIndex, ...filters, - }); + ...customFilters, + jfParams: { + ...filters.jfParams, + ...customFilters?.jfParams, + }, + ndParams: { + ...filters.ndParams, + ...customFilters?.ndParams, + }, + }; + + const queryKey = queryKeys.albums.list(server?.id || '', query); const albumsRes = await queryClient.fetchQuery( queryKey, async ({ signal }) => api.controller.getAlbumList({ - query: { - limit, - startIndex, - ...filters, - }, + query, server, signal, }), @@ -214,9 +242,30 @@ export const AlbumListHeader = ({ itemCount, gridRef, tableRef }: AlbumListHeade gridRef.current?.setItemData(data.items); } }, - [page.display, tableRef, setPagination, server, queryClient, gridRef, fetch], + [page.display, tableRef, customFilters, server, queryClient, setPagination, gridRef, fetch], ); + const handleOpenFiltersModal = () => { + openModal({ + children: ( + <> + {server?.type === ServerType.NAVIDROME ? ( + + ) : ( + + )} + + ), + title: 'Album Filters', + }); + }; + const handleRefresh = useCallback(() => { queryClient.invalidateQueries(queryKeys.albums.list(server?.id || '')); handleFilterChange(filters); @@ -315,7 +364,19 @@ export const AlbumListHeader = ({ itemCount, gridRef, tableRef }: AlbumListHeade const handlePlay = async (play: Play) => { if (!itemCount || itemCount === 0) return; - const query = { startIndex: 0, ...filters }; + const query = { + startIndex: 0, + ...filters, + ...customFilters, + jfParams: { + ...filters.jfParams, + ...customFilters?.jfParams, + }, + ndParams: { + ...filters.ndParams, + ...customFilters?.ndParams, + }, + }; const queryKey = queryKeys.albums.list(server?.id || '', query); const albumListRes = await queryClient.fetchQuery({ @@ -355,9 +416,11 @@ export const AlbumListHeader = ({ itemCount, gridRef, tableRef }: AlbumListHeade - Albums + {title || 'Albums'} )} - - - - - - {server?.type === ServerType.NAVIDROME ? ( - - ) : ( - - )} - - +