From e965bd266380377c0754bf536ebc99f879aa3572 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 16 Jul 2023 13:33:23 -0700 Subject: [PATCH] Update album artist list views --- .../components/album-artist-list-content.tsx | 372 ++---------------- .../album-artist-list-grid-view.tsx | 137 +++++++ .../album-artist-list-header-filters.tsx | 122 +++--- .../album-artist-list-table-view.tsx | 92 +++++ .../routes/album-artist-list-route.tsx | 1 + 5 files changed, 320 insertions(+), 404 deletions(-) create mode 100644 src/renderer/features/artists/components/album-artist-list-grid-view.tsx create mode 100644 src/renderer/features/artists/components/album-artist-list-table-view.tsx diff --git a/src/renderer/features/artists/components/album-artist-list-content.tsx b/src/renderer/features/artists/components/album-artist-list-content.tsx index 15c54120..1e72befe 100644 --- a/src/renderer/features/artists/components/album-artist-list-content.tsx +++ b/src/renderer/features/artists/components/album-artist-list-content.tsx @@ -1,343 +1,53 @@ -import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components'; -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 { ListOnScrollProps } from 'react-window'; -import { api } from '/@/renderer/api'; -import { queryKeys } from '/@/renderer/api/query-keys'; -import { AlbumArtist, AlbumArtistListSort, LibraryItem } from '/@/renderer/api/types'; -import { useQueryClient } from '@tanstack/react-query'; -import { useCurrentServer, useAlbumArtistListStore } from '/@/renderer/store'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import { - BodyScrollEvent, - ColDef, - GridReadyEvent, - IDatasource, - PaginationChangedEvent, - RowDoubleClickedEvent, -} 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'; -import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query'; -import { usePlayQueueAdd } from '/@/renderer/features/player'; -import { useAlbumArtistListFilter, useListStoreActions } from '../../../store/list.store'; -import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context'; -import { - VirtualInfiniteGridRef, - VirtualGridAutoSizerContainer, - VirtualInfiniteGrid, -} from '/@/renderer/components/virtual-grid'; -import { getColumnDefs, VirtualTable, TablePagination } from '/@/renderer/components/virtual-table'; +import { lazy, MutableRefObject, Suspense } from 'react'; +import { Spinner } from '/@/renderer/components'; +import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; +import { useAlbumArtistListStore } from '/@/renderer/store'; +import { ListDisplayType } from '/@/renderer/types'; + +const AlbumArtistListGridView = lazy(() => + import('/@/renderer/features/artists/components/album-artist-list-grid-view').then( + (module) => ({ + default: module.AlbumArtistListGridView, + }), + ), +); + +const AlbumArtistListTableView = lazy(() => + import('/@/renderer/features/artists/components/album-artist-list-table-view').then( + (module) => ({ + default: module.AlbumArtistListTableView, + }), + ), +); interface AlbumArtistListContentProps { gridRef: MutableRefObject; + itemCount?: number; tableRef: MutableRefObject; } -export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListContentProps) => { - const queryClient = useQueryClient(); - const navigate = useNavigate(); - const server = useCurrentServer(); - const handlePlayQueueAdd = usePlayQueueAdd(); - - const { id, pageKey } = useAlbumArtistListContext(); - const filter = useAlbumArtistListFilter({ id, key: pageKey }); - const { table, grid, display } = useAlbumArtistListStore(); - const { setTable, setTablePagination, setGrid } = useListStoreActions(); - - const isPaginationEnabled = display === ListDisplayType.TABLE_PAGINATED; - - const checkAlbumArtistList = useAlbumArtistList({ - options: { - cacheTime: Infinity, - staleTime: 60 * 1000 * 5, - }, - query: { - limit: 1, - startIndex: 0, - ...filter, - }, - serverId: server?.id, - }); - - const columnDefs: ColDef[] = useMemo(() => getColumnDefs(table.columns), [table.columns]); - - const onTableReady = useCallback( - (params: GridReadyEvent) => { - 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, - ...filter, - }); - - const albumArtistsRes = await queryClient.fetchQuery( - queryKey, - async ({ signal }) => - api.controller.getAlbumArtistList({ - apiClientProps: { - server, - signal, - }, - query: { - limit, - startIndex, - ...filter, - }, - }), - { cacheTime: 1000 * 60 * 1 }, - ); - - params.successCallback( - albumArtistsRes?.items || [], - albumArtistsRes?.totalRecordCount || 0, - ); - }, - rowCount: undefined, - }; - - params.api.setDatasource(dataSource); - params.api.ensureIndexVisible(table.scrollOffset || 0, 'top'); - }, - [filter, table.scrollOffset, queryClient, server], - ); - - const onTablePaginationChanged = useCallback( - (event: PaginationChangedEvent) => { - if (!isPaginationEnabled || !event.api) return; - - try { - // Scroll to top of page on pagination change - const currentPageStartIndex = - table.pagination.currentPage * table.pagination.itemsPerPage; - event.api?.ensureIndexVisible(currentPageStartIndex, 'top'); - } catch (err) { - console.log(err); - } - - setTablePagination({ - data: { - itemsPerPage: event.api.paginationGetPageSize(), - totalItems: event.api.paginationGetRowCount(), - totalPages: event.api.paginationGetTotalPages() + 1, - }, - key: pageKey, - }); - }, - [ - isPaginationEnabled, - pageKey, - setTablePagination, - table.pagination.currentPage, - table.pagination.itemsPerPage, - ], - ); - - const handleTableColumnChange = useCallback(() => { - const { columnApi } = tableRef?.current || {}; - const columnsOrder = columnApi?.getAllGridColumns(); - - if (!columnsOrder) return; - - const columnsInSettings = table.columns; - const updatedColumns = []; - for (const column of columnsOrder) { - const columnInSettings = columnsInSettings.find( - (c) => c.column === column.getColDef().colId, - ); - - if (columnInSettings) { - updatedColumns.push({ - ...columnInSettings, - ...(!table.autoFit && { - width: column.getColDef().width, - }), - }); - } - } - - setTable({ data: { columns: updatedColumns }, key: pageKey }); - }, [tableRef, table.columns, table.autoFit, setTable, pageKey]); - - const debouncedTableColumnChange = debounce(handleTableColumnChange, 200); - - const handleTableScroll = (e: BodyScrollEvent) => { - const scrollOffset = Number((e.top / table.rowHeight).toFixed(0)); - setTable({ data: { scrollOffset }, key: pageKey }); - }; - - const fetch = useCallback( - async ({ skip: startIndex, take: limit }: { skip: number; take: number }) => { - const queryKey = queryKeys.albumArtists.list(server?.id || '', { - limit, - startIndex, - ...filter, - }); - - const albumArtistsRes = await queryClient.fetchQuery( - queryKey, - async ({ signal }) => - api.controller.getAlbumArtistList({ - apiClientProps: { - server, - signal, - }, - query: { - limit, - startIndex, - ...filter, - }, - }), - { cacheTime: 1000 * 60 * 1 }, - ); - - return albumArtistsRes; - }, - [filter, queryClient, server], - ); - - const handleGridScroll = useCallback( - (e: ListOnScrollProps) => { - setGrid({ data: { scrollOffset: e.scrollOffset }, key: pageKey }); - }, - [pageKey, setGrid], - ); - - const handleGridSizeChange = () => { - if (table.autoFit) { - tableRef?.current?.api.sizeColumnsToFit(); - } - }; - - const cardRows = useMemo(() => { - const rows: CardRow[] = [ALBUMARTIST_CARD_ROWS.name]; - - switch (filter.sortBy) { - case AlbumArtistListSort.DURATION: - rows.push(ALBUMARTIST_CARD_ROWS.duration); - break; - case AlbumArtistListSort.FAVORITED: - break; - case AlbumArtistListSort.NAME: - break; - case AlbumArtistListSort.ALBUM_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.albumCount); - break; - case AlbumArtistListSort.PLAY_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.playCount); - break; - case AlbumArtistListSort.RANDOM: - break; - case AlbumArtistListSort.RATING: - rows.push(ALBUMARTIST_CARD_ROWS.rating); - break; - case AlbumArtistListSort.RECENTLY_ADDED: - break; - case AlbumArtistListSort.SONG_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.songCount); - break; - case AlbumArtistListSort.RELEASE_DATE: - break; - } - - return rows; - }, [filter.sortBy]); - - const handleContextMenu = useHandleTableContextMenu( - LibraryItem.ALBUM_ARTIST, - ALBUM_CONTEXT_MENU_ITEMS, - ); - - const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { - navigate(generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { albumArtistId: e.data.id })); - }; +export const AlbumArtistListContent = ({ + itemCount, + gridRef, + tableRef, +}: AlbumArtistListContentProps) => { + const { display } = useAlbumArtistListStore(); + const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER; return ( - <> - - {display === ListDisplayType.CARD || display === ListDisplayType.POSTER ? ( - - {({ height, width }) => ( - <> - - - )} - - ) : ( - data.data.id} - infiniteInitialRowCount={checkAlbumArtistList.data?.totalRecordCount || 1} - pagination={isPaginationEnabled} - paginationAutoPageSize={isPaginationEnabled} - paginationPageSize={table.pagination.itemsPerPage || 100} - rowHeight={table.rowHeight || 40} - rowModelType="infinite" - onBodyScrollEnd={handleTableScroll} - onCellContextMenu={handleContextMenu} - onColumnMoved={handleTableColumnChange} - onColumnResized={debouncedTableColumnChange} - onGridReady={onTableReady} - onGridSizeChanged={handleGridSizeChange} - onPaginationChanged={onTablePaginationChanged} - onRowDoubleClicked={handleRowDoubleClick} - /> - )} - - {isPaginationEnabled && ( - - {display === ListDisplayType.TABLE_PAGINATED && ( - - )} - + }> + {isGrid ? ( + + ) : ( + )} - + ); }; diff --git a/src/renderer/features/artists/components/album-artist-list-grid-view.tsx b/src/renderer/features/artists/components/album-artist-list-grid-view.tsx new file mode 100644 index 00000000..fa8b21f8 --- /dev/null +++ b/src/renderer/features/artists/components/album-artist-list-grid-view.tsx @@ -0,0 +1,137 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { MutableRefObject, useCallback, useMemo } from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { ListOnScrollProps } from 'react-window'; +import { VirtualGridAutoSizerContainer } from '../../../components/virtual-grid/virtual-grid-wrapper'; +import { api } from '/@/renderer/api'; +import { queryKeys } from '/@/renderer/api/query-keys'; +import { AlbumArtist, AlbumArtistListSort, LibraryItem } from '/@/renderer/api/types'; +import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components'; +import { VirtualInfiniteGrid, VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; +import { useAlbumArtistListContext } from '/@/renderer/features/artists/context/album-artist-list-context'; +import { usePlayQueueAdd } from '/@/renderer/features/player'; +import { AppRoute } from '/@/renderer/router/routes'; +import { + useAlbumArtistListFilter, + useAlbumArtistListStore, + useCurrentServer, + useListStoreActions, +} from '/@/renderer/store'; +import { CardRow, ListDisplayType } from '/@/renderer/types'; + +interface AlbumArtistListGridViewProps { + gridRef: MutableRefObject; + itemCount?: number; +} + +export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListGridViewProps) => { + const queryClient = useQueryClient(); + const server = useCurrentServer(); + const handlePlayQueueAdd = usePlayQueueAdd(); + + const { id, pageKey } = useAlbumArtistListContext(); + const filter = useAlbumArtistListFilter({ id, key: pageKey }); + const { grid, display } = useAlbumArtistListStore(); + const { setGrid } = useListStoreActions(); + + const fetch = useCallback( + async ({ skip: startIndex, take: limit }: { skip: number; take: number }) => { + const queryKey = queryKeys.albumArtists.list(server?.id || '', { + limit, + startIndex, + ...filter, + }); + + const albumArtistsRes = await queryClient.fetchQuery( + queryKey, + async ({ signal }) => + api.controller.getAlbumArtistList({ + apiClientProps: { + server, + signal, + }, + query: { + limit, + startIndex, + ...filter, + }, + }), + { cacheTime: 1000 * 60 * 1 }, + ); + + return albumArtistsRes; + }, + [filter, queryClient, server], + ); + + const handleGridScroll = useCallback( + (e: ListOnScrollProps) => { + setGrid({ data: { scrollOffset: e.scrollOffset }, key: pageKey }); + }, + [pageKey, setGrid], + ); + + const cardRows = useMemo(() => { + const rows: CardRow[] = [ALBUMARTIST_CARD_ROWS.name]; + + switch (filter.sortBy) { + case AlbumArtistListSort.DURATION: + rows.push(ALBUMARTIST_CARD_ROWS.duration); + break; + case AlbumArtistListSort.FAVORITED: + break; + case AlbumArtistListSort.NAME: + break; + case AlbumArtistListSort.ALBUM_COUNT: + rows.push(ALBUMARTIST_CARD_ROWS.albumCount); + break; + case AlbumArtistListSort.PLAY_COUNT: + rows.push(ALBUMARTIST_CARD_ROWS.playCount); + break; + case AlbumArtistListSort.RANDOM: + break; + case AlbumArtistListSort.RATING: + rows.push(ALBUMARTIST_CARD_ROWS.rating); + break; + case AlbumArtistListSort.RECENTLY_ADDED: + break; + case AlbumArtistListSort.SONG_COUNT: + rows.push(ALBUMARTIST_CARD_ROWS.songCount); + break; + case AlbumArtistListSort.RELEASE_DATE: + break; + } + + return rows; + }, [filter.sortBy]); + + return ( + + + {({ height, width }) => ( + + )} + + + ); +}; diff --git a/src/renderer/features/artists/components/album-artist-list-header-filters.tsx b/src/renderer/features/artists/components/album-artist-list-header-filters.tsx index d994ca5c..733ecb12 100644 --- a/src/renderer/features/artists/components/album-artist-list-header-filters.tsx +++ b/src/renderer/features/artists/components/album-artist-list-header-filters.tsx @@ -1,34 +1,27 @@ -import { useCallback, ChangeEvent, MutableRefObject, MouseEvent } from 'react'; import { IDatasource } from '@ag-grid-community/core'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import { Group, Stack, Flex } from '@mantine/core'; +import { Divider, Flex, Group, Stack } from '@mantine/core'; import { useQueryClient } from '@tanstack/react-query'; import debounce from 'lodash/debounce'; -import { - RiSortAsc, - RiSortDesc, - RiFolder2Line, - RiMoreFill, - RiRefreshLine, - RiSettings3Fill, -} from 'react-icons/ri'; +import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react'; +import { RiFolder2Line, RiMoreFill, RiRefreshLine, RiSettings3Fill } from 'react-icons/ri'; +import { useAlbumArtistListContext } from '../context/album-artist-list-context'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/renderer/api/types'; -import { DropdownMenu, Text, Button, Slider, MultiSelect, Switch } from '/@/renderer/components'; -import { useMusicFolders } from '/@/renderer/features/shared'; -import { useContainerQuery } from '/@/renderer/hooks'; -import { - useCurrentServer, - useAlbumArtistListStore, - AlbumArtistListFilter, - useListStoreActions, - useAlbumArtistListFilter, -} from '/@/renderer/store'; -import { ListDisplayType, TableColumn, ServerType } from '/@/renderer/types'; -import { useAlbumArtistListContext } from '../context/album-artist-list-context'; +import { Button, DropdownMenu, MultiSelect, Slider, Switch, Text } from '/@/renderer/components'; import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid'; import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table'; +import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared'; +import { useContainerQuery } from '/@/renderer/hooks'; +import { + AlbumArtistListFilter, + useAlbumArtistListFilter, + useAlbumArtistListStore, + useCurrentServer, + useListStoreActions, +} from '/@/renderer/store'; +import { ListDisplayType, ServerType, TableColumn } from '/@/renderer/types'; const FILTERS = { jellyfin: [ @@ -61,11 +54,6 @@ const FILTERS = { ], }; -const ORDER = [ - { name: 'Ascending', value: SortOrder.ASC }, - { name: 'Descending', value: SortOrder.DESC }, -]; - interface AlbumArtistListHeaderFiltersProps { gridRef: MutableRefObject; tableRef: MutableRefObject; @@ -84,6 +72,7 @@ export const AlbumArtistListHeaderFilters = ({ const filter = useAlbumArtistListFilter({ key: pageKey }); const cq = useContainerQuery(); + const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER; const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id }); const sortByLabel = @@ -92,8 +81,6 @@ export const AlbumArtistListHeaderFilters = ({ ?.name) || 'Unknown'; - const sortOrderLabel = ORDER.find((o) => o.value === filter.sortOrder)?.name || 'Unknown'; - const handleItemSize = (e: number) => { if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) { setTable({ data: { rowHeight: e }, key: pageKey }); @@ -332,51 +319,41 @@ export const AlbumArtistListHeaderFilters = ({ ))} - + + {server?.type === ServerType.JELLYFIN && ( - - - - - - {musicFoldersQuery.data?.items.map((folder) => ( - + + + + + + + {musicFoldersQuery.data?.items.map((folder) => ( + + {folder.name} + + ))} + + + )} +