Simplify list store and table implementation
This commit is contained in:
parent
9bcefb3105
commit
8b4a2d1ac0
2 changed files with 135 additions and 49 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
BodyScrollEvent,
|
BodyScrollEvent,
|
||||||
ColDef,
|
ColDef,
|
||||||
|
@ -11,51 +12,45 @@ import {
|
||||||
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 { QueryKey, useQueryClient } from '@tanstack/react-query';
|
import { QueryKey, useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
|
||||||
import { generatePath, useNavigate } from 'react-router';
|
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 { BasePaginatedResponse, LibraryItem } from '/@/renderer/api/types';
|
||||||
import { getColumnDefs, VirtualTableProps } from '/@/renderer/components/virtual-table';
|
import { getColumnDefs, VirtualTableProps } from '/@/renderer/components/virtual-table';
|
||||||
import { SetContextMenuItems, useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
import { SetContextMenuItems, useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { ListDeterministicArgs, ListItemProps, ListTableProps } from '/@/renderer/store';
|
import { useListStoreActions } from '/@/renderer/store';
|
||||||
import { ListDisplayType, ServerListItem, TablePagination } from '/@/renderer/types';
|
import { ListDisplayType, ServerListItem } from '/@/renderer/types';
|
||||||
|
import { useListStoreByKey } from '../../../store/list.store';
|
||||||
|
|
||||||
export type AgGridFetchFn<TResponse, TFilter> = (
|
export type AgGridFetchFn<TResponse, TFilter> = (
|
||||||
args: { filter: TFilter; limit: number; startIndex: number },
|
args: { filter: TFilter; limit: number; startIndex: number },
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
) => Promise<TResponse>;
|
) => Promise<TResponse>;
|
||||||
|
|
||||||
interface UseAgGridProps<TResponse, TFilter> {
|
interface UseAgGridProps<TFilter> {
|
||||||
contextMenu: SetContextMenuItems;
|
contextMenu: SetContextMenuItems;
|
||||||
fetch: {
|
customFilters?: Partial<TFilter>;
|
||||||
filter: TFilter;
|
|
||||||
fn: AgGridFetchFn<TResponse, TFilter>;
|
|
||||||
itemCount?: number;
|
|
||||||
queryKey: (id: string, query?: Record<any, any>) => QueryKey;
|
|
||||||
server: ServerListItem | null;
|
|
||||||
};
|
|
||||||
itemCount?: number;
|
itemCount?: number;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
properties: ListItemProps<any>;
|
server: ServerListItem | null;
|
||||||
setTable: (args: { data: Partial<ListTableProps> } & ListDeterministicArgs) => void;
|
|
||||||
setTablePagination: (args: { data: Partial<TablePagination> } & ListDeterministicArgs) => void;
|
|
||||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useVirtualTable = <TResponse, TFilter>({
|
export const useVirtualTable = <TFilter>({
|
||||||
fetch,
|
server,
|
||||||
tableRef,
|
tableRef,
|
||||||
properties,
|
|
||||||
setTable,
|
|
||||||
setTablePagination,
|
|
||||||
pageKey,
|
pageKey,
|
||||||
itemType,
|
itemType,
|
||||||
contextMenu,
|
contextMenu,
|
||||||
itemCount,
|
itemCount,
|
||||||
}: UseAgGridProps<TResponse, TFilter>) => {
|
customFilters,
|
||||||
|
}: UseAgGridProps<TFilter>) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { setTable, setTablePagination } = useListStoreActions();
|
||||||
|
const properties = useListStoreByKey({ filter: customFilters, key: pageKey });
|
||||||
|
|
||||||
const isPaginationEnabled = properties.display === ListDisplayType.TABLE_PAGINATED;
|
const isPaginationEnabled = properties.display === ListDisplayType.TABLE_PAGINATED;
|
||||||
|
|
||||||
|
@ -77,6 +72,43 @@ export const useVirtualTable = <TResponse, TFilter>({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const queryKeyFn:
|
||||||
|
| ((serverId: string, query: Record<any, any>, 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<BasePaginatedResponse<any> | 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(
|
const onGridReady = useCallback(
|
||||||
(params: GridReadyEvent) => {
|
(params: GridReadyEvent) => {
|
||||||
const dataSource: IDatasource = {
|
const dataSource: IDatasource = {
|
||||||
|
@ -84,29 +116,32 @@ export const useVirtualTable = <TResponse, TFilter>({
|
||||||
const limit = params.endRow - params.startRow;
|
const limit = params.endRow - params.startRow;
|
||||||
const startIndex = params.startRow;
|
const startIndex = params.startRow;
|
||||||
|
|
||||||
const queryKey = fetch.queryKey(fetch.server?.id || '', {
|
const queryKey = queryKeyFn!(
|
||||||
limit,
|
server?.id || '',
|
||||||
startIndex,
|
{
|
||||||
...fetch.filter,
|
...(properties.filter as any),
|
||||||
});
|
|
||||||
|
|
||||||
const results = (await queryClient.fetchQuery(
|
|
||||||
queryKey,
|
|
||||||
async ({ signal }) => {
|
|
||||||
const res = await fetch.fn(
|
|
||||||
{
|
|
||||||
filter: fetch.filter,
|
|
||||||
limit,
|
|
||||||
startIndex,
|
|
||||||
},
|
|
||||||
signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
limit,
|
||||||
|
startIndex,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
const results = (await queryClient.fetchQuery(queryKey, async ({ signal }) => {
|
||||||
)) as BasePaginatedResponse<any>;
|
const res = await queryFn!({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
...properties.filter,
|
||||||
|
limit,
|
||||||
|
startIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
})) as BasePaginatedResponse<any>;
|
||||||
|
|
||||||
params.successCallback(results?.items || [], results?.totalRecordCount || 0);
|
params.successCallback(results?.items || [], results?.totalRecordCount || 0);
|
||||||
},
|
},
|
||||||
|
@ -116,7 +151,14 @@ export const useVirtualTable = <TResponse, TFilter>({
|
||||||
params.api.setDatasource(dataSource);
|
params.api.setDatasource(dataSource);
|
||||||
params.api.ensureIndexVisible(properties.table.scrollOffset || 0, 'top');
|
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(
|
const onPaginationChanged = useCallback(
|
||||||
|
@ -228,6 +270,13 @@ export const useVirtualTable = <TResponse, TFilter>({
|
||||||
case LibraryItem.ALBUM:
|
case LibraryItem.ALBUM:
|
||||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
||||||
break;
|
break;
|
||||||
|
case LibraryItem.ALBUM_ARTIST:
|
||||||
|
navigate(
|
||||||
|
generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||||
|
albumArtistId: e.data.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
break;
|
||||||
case LibraryItem.ARTIST:
|
case LibraryItem.ARTIST:
|
||||||
navigate(
|
navigate(
|
||||||
generatePath(AppRoute.LIBRARY_ARTISTS_DETAIL, { artistId: e.data.id }),
|
generatePath(AppRoute.LIBRARY_ARTISTS_DETAIL, { artistId: e.data.id }),
|
||||||
|
|
|
@ -65,11 +65,20 @@ export type ListDeterministicArgs = { key: ListKey };
|
||||||
|
|
||||||
export interface ListSlice extends ListState {
|
export interface ListSlice extends ListState {
|
||||||
_actions: {
|
_actions: {
|
||||||
getFilter: (args: { id?: string; itemType: LibraryItem; key?: string }) => FilterType;
|
getFilter: (args: {
|
||||||
|
customFilters?: Record<any, any>;
|
||||||
|
id?: string;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
key?: string;
|
||||||
|
}) => FilterType;
|
||||||
resetFilter: () => void;
|
resetFilter: () => void;
|
||||||
setDisplayType: (args: { data: ListDisplayType } & ListDeterministicArgs) => void;
|
setDisplayType: (args: { data: ListDisplayType } & ListDeterministicArgs) => void;
|
||||||
setFilter: (
|
setFilter: (
|
||||||
args: { data: Partial<FilterType>; itemType: LibraryItem } & ListDeterministicArgs,
|
args: {
|
||||||
|
customFilters?: Record<any, any>;
|
||||||
|
data: Partial<FilterType>;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
} & ListDeterministicArgs,
|
||||||
) => FilterType;
|
) => FilterType;
|
||||||
setGrid: (args: { data: Partial<ListGridProps> } & ListDeterministicArgs) => void;
|
setGrid: (args: { data: Partial<ListGridProps> } & ListDeterministicArgs) => void;
|
||||||
setStore: (data: Partial<ListSlice>) => void;
|
setStore: (data: Partial<ListSlice>) => void;
|
||||||
|
@ -168,11 +177,14 @@ export const useListStore = create<ListSlice>()(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return get()._actions.getFilter({
|
return {
|
||||||
id,
|
...get()._actions.getFilter({
|
||||||
itemType: args.itemType,
|
id,
|
||||||
key: args.key,
|
itemType: args.itemType,
|
||||||
});
|
key: args.key,
|
||||||
|
}),
|
||||||
|
...args.customFilters,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
setGrid: (args) => {
|
setGrid: (args) => {
|
||||||
const [page, id] = args.key.split('_');
|
const [page, id] = args.key.split('_');
|
||||||
|
@ -494,6 +506,31 @@ export const useListStore = create<ListSlice>()(
|
||||||
|
|
||||||
export const useListStoreActions = () => useListStore((state) => state._actions);
|
export const useListStoreActions = () => useListStore((state) => state._actions);
|
||||||
|
|
||||||
|
export const useListStoreByKey = <TFilter>(args: { filter?: Partial<TFilter>; 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 = <TFilter>(args: { filter?: Partial<TFilter>; 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 }) =>
|
export const useAlbumListStore = (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;
|
||||||
|
|
Reference in a new issue