From 8b4a2d1ac07ca10959ec21b04b15beb7ce34eb8a Mon Sep 17 00:00:00 2001 From: jeffvli Date: Wed, 19 Jul 2023 14:22:24 -0700 Subject: [PATCH] Simplify list store and table implementation --- .../virtual-table/hooks/use-virtual-table.ts | 133 ++++++++++++------ src/renderer/store/list.store.ts | 51 ++++++- 2 files changed, 135 insertions(+), 49 deletions(-) diff --git a/src/renderer/components/virtual-table/hooks/use-virtual-table.ts b/src/renderer/components/virtual-table/hooks/use-virtual-table.ts index 403f2abd..7319ec59 100644 --- a/src/renderer/components/virtual-table/hooks/use-virtual-table.ts +++ b/src/renderer/components/virtual-table/hooks/use-virtual-table.ts @@ -1,3 +1,4 @@ +import { MutableRefObject, useCallback, useMemo } from 'react'; import { BodyScrollEvent, ColDef, @@ -11,51 +12,45 @@ import { import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { QueryKey, useQueryClient } from '@tanstack/react-query'; import debounce from 'lodash/debounce'; -import { MutableRefObject, useCallback, useMemo } from 'react'; import { generatePath, useNavigate } from 'react-router'; +import { api } from '/@/renderer/api'; +import { QueryPagination, queryKeys } from '/@/renderer/api/query-keys'; import { BasePaginatedResponse, LibraryItem } from '/@/renderer/api/types'; import { getColumnDefs, VirtualTableProps } from '/@/renderer/components/virtual-table'; import { SetContextMenuItems, useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { AppRoute } from '/@/renderer/router/routes'; -import { ListDeterministicArgs, ListItemProps, ListTableProps } from '/@/renderer/store'; -import { ListDisplayType, ServerListItem, TablePagination } from '/@/renderer/types'; +import { useListStoreActions } from '/@/renderer/store'; +import { ListDisplayType, ServerListItem } from '/@/renderer/types'; +import { useListStoreByKey } from '../../../store/list.store'; export type AgGridFetchFn = ( args: { filter: TFilter; limit: number; startIndex: number }, signal?: AbortSignal, ) => Promise; -interface UseAgGridProps { +interface UseAgGridProps { contextMenu: SetContextMenuItems; - fetch: { - filter: TFilter; - fn: AgGridFetchFn; - itemCount?: number; - queryKey: (id: string, query?: Record) => QueryKey; - server: ServerListItem | null; - }; + customFilters?: Partial; itemCount?: number; itemType: LibraryItem; pageKey: string; - properties: ListItemProps; - setTable: (args: { data: Partial } & ListDeterministicArgs) => void; - setTablePagination: (args: { data: Partial } & ListDeterministicArgs) => void; + server: ServerListItem | null; tableRef: MutableRefObject; } -export const useVirtualTable = ({ - fetch, +export const useVirtualTable = ({ + server, tableRef, - properties, - setTable, - setTablePagination, pageKey, itemType, contextMenu, itemCount, -}: UseAgGridProps) => { + customFilters, +}: UseAgGridProps) => { const queryClient = useQueryClient(); const navigate = useNavigate(); + const { setTable, setTablePagination } = useListStoreActions(); + const properties = useListStoreByKey({ filter: customFilters, key: pageKey }); const isPaginationEnabled = properties.display === ListDisplayType.TABLE_PAGINATED; @@ -77,6 +72,43 @@ export const useVirtualTable = ({ } }; + const queryKeyFn: + | ((serverId: string, query: Record, pagination: QueryPagination) => QueryKey) + | null = useMemo(() => { + if (itemType === LibraryItem.ALBUM) { + return queryKeys.albums.list; + } + if (itemType === LibraryItem.ALBUM_ARTIST) { + return queryKeys.albumArtists.list; + } + if (itemType === LibraryItem.PLAYLIST) { + return queryKeys.playlists.list; + } + if (itemType === LibraryItem.SONG) { + return queryKeys.songs.list; + } + + return null; + }, [itemType]); + + const queryFn: ((args: any) => Promise | null | undefined>) | null = + useMemo(() => { + if (itemType === LibraryItem.ALBUM) { + return api.controller.getAlbumList; + } + if (itemType === LibraryItem.ALBUM_ARTIST) { + return api.controller.getAlbumArtistList; + } + if (itemType === LibraryItem.PLAYLIST) { + return api.controller.getPlaylistList; + } + if (itemType === LibraryItem.SONG) { + return api.controller.getSongList; + } + + return null; + }, [itemType]); + const onGridReady = useCallback( (params: GridReadyEvent) => { const dataSource: IDatasource = { @@ -84,29 +116,32 @@ export const useVirtualTable = ({ const limit = params.endRow - params.startRow; const startIndex = params.startRow; - const queryKey = fetch.queryKey(fetch.server?.id || '', { - limit, - startIndex, - ...fetch.filter, - }); - - const results = (await queryClient.fetchQuery( - queryKey, - async ({ signal }) => { - const res = await fetch.fn( - { - filter: fetch.filter, - limit, - startIndex, - }, - signal, - ); - - return res; + const queryKey = queryKeyFn!( + server?.id || '', + { + ...(properties.filter as any), }, + { + limit, + startIndex, + }, + ); - { cacheTime: 1000 * 60 * 1 }, - )) as BasePaginatedResponse; + const results = (await queryClient.fetchQuery(queryKey, async ({ signal }) => { + const res = await queryFn!({ + apiClientProps: { + server, + signal, + }, + query: { + ...properties.filter, + limit, + startIndex, + }, + }); + + return res; + })) as BasePaginatedResponse; params.successCallback(results?.items || [], results?.totalRecordCount || 0); }, @@ -116,7 +151,14 @@ export const useVirtualTable = ({ params.api.setDatasource(dataSource); params.api.ensureIndexVisible(properties.table.scrollOffset || 0, 'top'); }, - [fetch, properties.table.scrollOffset, queryClient], + [ + properties.table.scrollOffset, + properties.filter, + queryKeyFn, + server, + queryClient, + queryFn, + ], ); const onPaginationChanged = useCallback( @@ -228,6 +270,13 @@ export const useVirtualTable = ({ case LibraryItem.ALBUM: navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id })); break; + case LibraryItem.ALBUM_ARTIST: + navigate( + generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { + albumArtistId: e.data.id, + }), + ); + break; case LibraryItem.ARTIST: navigate( generatePath(AppRoute.LIBRARY_ARTISTS_DETAIL, { artistId: e.data.id }), diff --git a/src/renderer/store/list.store.ts b/src/renderer/store/list.store.ts index a716355a..654b3ecd 100644 --- a/src/renderer/store/list.store.ts +++ b/src/renderer/store/list.store.ts @@ -65,11 +65,20 @@ export type ListDeterministicArgs = { key: ListKey }; export interface ListSlice extends ListState { _actions: { - getFilter: (args: { id?: string; itemType: LibraryItem; key?: string }) => FilterType; + getFilter: (args: { + customFilters?: Record; + id?: string; + itemType: LibraryItem; + key?: string; + }) => FilterType; resetFilter: () => void; setDisplayType: (args: { data: ListDisplayType } & ListDeterministicArgs) => void; setFilter: ( - args: { data: Partial; itemType: LibraryItem } & ListDeterministicArgs, + args: { + customFilters?: Record; + data: Partial; + itemType: LibraryItem; + } & ListDeterministicArgs, ) => FilterType; setGrid: (args: { data: Partial } & ListDeterministicArgs) => void; setStore: (data: Partial) => void; @@ -168,11 +177,14 @@ export const useListStore = create()( } }); - return get()._actions.getFilter({ - id, - itemType: args.itemType, - key: args.key, - }); + return { + ...get()._actions.getFilter({ + id, + itemType: args.itemType, + key: args.key, + }), + ...args.customFilters, + }; }, setGrid: (args) => { const [page, id] = args.key.split('_'); @@ -494,6 +506,31 @@ export const useListStore = create()( export const useListStoreActions = () => useListStore((state) => state._actions); +export const useListStoreByKey = (args: { filter?: Partial; key: string }) => { + const key = args.key as keyof ListState['item']; + return useListStore( + (state) => ({ + ...state.item[key], + filter: { + ...state.item[key].filter, + ...args.filter, + }, + }), + shallow, + ); +}; + +export const useListFilterByKey = (args: { filter?: Partial; key: string }) => { + const key = args.key as keyof ListState['item']; + return useListStore( + (state) => ({ + ...state.item[key].filter, + ...args.filter, + }), + shallow, + ); +}; + export const useAlbumListStore = (args?: { id?: string; key?: string }) => useListStore((state) => { const detail = args?.key ? state.detail[args.key] : undefined;