Add album table view

This commit is contained in:
jeffvli 2022-12-28 01:44:49 -08:00
parent e5ad41b9da
commit b967c8cb19
5 changed files with 461 additions and 85 deletions

View file

@ -34,6 +34,21 @@ export const SONG_TABLE_COLUMNS = [
// { label: 'Skip', value: TableColumn.SKIP }, // { label: 'Skip', value: TableColumn.SKIP },
]; ];
export const ALBUM_TABLE_COLUMNS = [
{ label: 'Row Index', value: TableColumn.ROW_INDEX },
{ label: 'Title', value: TableColumn.TITLE },
{ label: 'Title (Combined)', value: TableColumn.TITLE_COMBINED },
{ label: 'Duration', value: TableColumn.DURATION },
{ label: 'Album Artist', value: TableColumn.ALBUM_ARTIST },
{ label: 'Artist', value: TableColumn.ARTIST },
{ label: 'Genre', value: TableColumn.GENRE },
{ label: 'Year', value: TableColumn.YEAR },
{ label: 'Release Date', value: TableColumn.RELEASE_DATE },
{ label: 'Last Played', value: TableColumn.LAST_PLAYED },
{ label: 'Date Added', value: TableColumn.DATE_ADDED },
{ label: 'Plays', value: TableColumn.PLAY_COUNT },
];
interface TableConfigDropdownProps { interface TableConfigDropdownProps {
type: TableType; type: TableType;
} }

View file

@ -1,8 +1,11 @@
import { import {
ALBUM_CARD_ROWS, ALBUM_CARD_ROWS,
getColumnDefs,
TablePagination,
VirtualGridAutoSizerContainer, VirtualGridAutoSizerContainer,
VirtualInfiniteGrid, VirtualInfiniteGrid,
VirtualInfiniteGridRef, VirtualInfiniteGridRef,
VirtualTable,
} from '/@/renderer/components'; } from '/@/renderer/components';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { ListDisplayType, CardRow, LibraryItem } from '/@/renderer/types'; import { ListDisplayType, CardRow, LibraryItem } from '/@/renderer/types';
@ -16,25 +19,152 @@ import { Album, AlbumListSort } from '/@/renderer/api/types';
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query'; import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add'; import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useCurrentServer, useSetAlbumStore, useAlbumListStore } from '/@/renderer/store'; import {
useCurrentServer,
useSetAlbumStore,
useAlbumListStore,
useAlbumTablePagination,
useSetAlbumTable,
useSetAlbumTablePagination,
} from '/@/renderer/store';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import {
BodyScrollEvent,
ColDef,
GridReadyEvent,
IDatasource,
PaginationChangedEvent,
} from '@ag-grid-community/core';
import { AnimatePresence } from 'framer-motion';
import debounce from 'lodash/debounce';
interface AlbumListContentProps { interface AlbumListContentProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>; gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
tableRef: MutableRefObject<AgGridReactType | null>;
} }
export const AlbumListContent = ({ gridRef }: AlbumListContentProps) => { export const AlbumListContent = ({ gridRef, tableRef }: AlbumListContentProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const server = useCurrentServer(); const server = useCurrentServer();
const page = useAlbumListStore(); const page = useAlbumListStore();
const setPage = useSetAlbumStore(); const setPage = useSetAlbumStore();
const handlePlayQueueAdd = useHandlePlayQueueAdd(); const handlePlayQueueAdd = useHandlePlayQueueAdd();
const albumListQuery = useAlbumList({ const pagination = useAlbumTablePagination();
const setPagination = useSetAlbumTablePagination();
const setTable = useSetAlbumTable();
const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED;
const checkAlbumList = useAlbumList({
limit: 1, limit: 1,
startIndex: 0, startIndex: 0,
...page.filter, ...page.filter,
}); });
const columnDefs: ColDef[] = useMemo(
() => getColumnDefs(page.table.columns),
[page.table.columns],
);
const defaultColumnDefs: ColDef = useMemo(() => {
return {
lockPinned: true,
lockVisible: true,
resizable: true,
};
}, []);
const onTableReady = useCallback(
(params: GridReadyEvent) => {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
const queryKey = queryKeys.albums.list(server?.id || '', {
limit,
startIndex,
...page.filter,
});
const albumsRes = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
api.controller.getAlbumList({
query: {
limit,
startIndex,
...page.filter,
},
server,
signal,
}),
);
const albums = api.normalize.albumList(albumsRes, server);
params.successCallback(albums?.items || [], albumsRes?.totalRecordCount || undefined);
},
rowCount: undefined,
};
params.api.setDatasource(dataSource);
// params.api.ensureIndexVisible(page.table.scrollOffset || 0, 'top');
},
[page.filter, queryClient, server],
);
const onTablePaginationChanged = useCallback(
(event: PaginationChangedEvent) => {
if (!isPaginationEnabled || !event.api) return;
// Scroll to top of page on pagination change
const currentPageStartIndex = pagination.currentPage * pagination.itemsPerPage;
event.api?.ensureIndexVisible(currentPageStartIndex, 'top');
setPagination({
itemsPerPage: event.api.paginationGetPageSize(),
totalItems: event.api.paginationGetRowCount(),
totalPages: event.api.paginationGetTotalPages() + 1,
});
},
[isPaginationEnabled, pagination.currentPage, pagination.itemsPerPage, setPagination],
);
const handleTableSizeChange = () => {
if (page.table.autoFit) {
tableRef?.current?.api.sizeColumnsToFit();
}
};
const handleTableColumnChange = useCallback(() => {
const { columnApi } = tableRef?.current || {};
const columnsOrder = columnApi?.getAllGridColumns();
if (!columnsOrder) return;
const columnsInSettings = page.table.columns;
const updatedColumns = [];
for (const column of columnsOrder) {
const columnInSettings = columnsInSettings.find((c) => c.column === column.getColDef().colId);
if (columnInSettings) {
updatedColumns.push({
...columnInSettings,
...(!page.table.autoFit && {
width: column.getColDef().width,
}),
});
}
}
setTable({ columns: updatedColumns });
}, [page.table.autoFit, page.table.columns, setTable, tableRef]);
const debouncedTableColumnChange = debounce(handleTableColumnChange, 200);
const handleTableScroll = (e: BodyScrollEvent) => {
const scrollOffset = Number((e.top / page.table.rowHeight).toFixed(0));
setTable({ scrollOffset });
};
const fetch = useCallback( const fetch = useCallback(
async ({ skip, take }: { skip: number; take: number }) => { async ({ skip, take }: { skip: number; take: number }) => {
const queryKey = queryKeys.albums.list(server?.id || '', { const queryKey = queryKeys.albums.list(server?.id || '', {
@ -139,10 +269,13 @@ export const AlbumListContent = ({ gridRef }: AlbumListContentProps) => {
}, [page.filter.sortBy]); }, [page.filter.sortBy]);
return ( return (
<>
<VirtualGridAutoSizerContainer> <VirtualGridAutoSizerContainer>
{page.display === ListDisplayType.CARD || page.display === ListDisplayType.POSTER ? (
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<VirtualInfiniteGrid <VirtualInfiniteGrid
key={`album-list-${server?.id}-${page.display}`}
ref={gridRef} ref={gridRef}
cardRows={cardRows} cardRows={cardRows}
display={page.display || ListDisplayType.CARD} display={page.display || ListDisplayType.CARD}
@ -150,7 +283,7 @@ export const AlbumListContent = ({ gridRef }: AlbumListContentProps) => {
handlePlayQueueAdd={handlePlayQueueAdd} handlePlayQueueAdd={handlePlayQueueAdd}
height={height} height={height}
initialScrollOffset={page?.grid.scrollOffset || 0} initialScrollOffset={page?.grid.scrollOffset || 0}
itemCount={albumListQuery?.data?.totalRecordCount || 0} itemCount={checkAlbumList?.data?.totalRecordCount || 0}
itemGap={20} itemGap={20}
itemSize={150 + page.grid?.size} itemSize={150 + page.grid?.size}
itemType={LibraryItem.ALBUM} itemType={LibraryItem.ALBUM}
@ -164,6 +297,60 @@ export const AlbumListContent = ({ gridRef }: AlbumListContentProps) => {
/> />
)} )}
</AutoSizer> </AutoSizer>
) : (
<VirtualTable
// https://github.com/ag-grid/ag-grid/issues/5284
// 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}
alwaysShowHorizontalScroll
animateRows
maintainColumnOrder
suppressCopyRowsToClipboard
suppressMoveWhenRowDragging
suppressPaginationPanel
suppressRowDrag
suppressScrollOnNewData
blockLoadDebounceMillis={200}
cacheBlockSize={500}
cacheOverflowSize={1}
columnDefs={columnDefs}
defaultColDef={defaultColumnDefs}
enableCellChangeFlash={false}
getRowId={(data) => data.data.id}
infiniteInitialRowCount={checkAlbumList.data?.totalRecordCount || 100}
pagination={isPaginationEnabled}
paginationAutoPageSize={isPaginationEnabled}
paginationPageSize={page.table.pagination.itemsPerPage || 100}
rowBuffer={20}
rowHeight={page.table.rowHeight || 40}
rowModelType="infinite"
rowSelection="multiple"
onBodyScrollEnd={handleTableScroll}
onCellContextMenu={(e) => console.log('context', e)}
onColumnMoved={handleTableColumnChange}
onColumnResized={debouncedTableColumnChange}
onGridReady={onTableReady}
onGridSizeChanged={handleTableSizeChange}
onPaginationChanged={onTablePaginationChanged}
/>
)}
</VirtualGridAutoSizerContainer> </VirtualGridAutoSizerContainer>
{isPaginationEnabled && (
<AnimatePresence
presenceAffectsLayout
initial={false}
mode="wait"
>
{page.display === ListDisplayType.TABLE_PAGINATED && (
<TablePagination
pagination={pagination}
setPagination={setPagination}
tableRef={tableRef}
/>
)}
</AnimatePresence>
)}
</>
); );
}; };

View file

@ -1,9 +1,10 @@
import { Flex, Slider } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import type { ChangeEvent, MouseEvent, MutableRefObject } from 'react'; import type { ChangeEvent, MouseEvent, MutableRefObject } from 'react';
import { useCallback } 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 { Flex, Group, Stack } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query';
import debounce from 'lodash/debounce';
import { import {
RiArrowDownSLine, RiArrowDownSLine,
RiFilter3Line, RiFilter3Line,
@ -18,11 +19,16 @@ import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { AlbumListSort, ServerType, SortOrder } from '/@/renderer/api/types'; import { AlbumListSort, ServerType, SortOrder } from '/@/renderer/api/types';
import { import {
ALBUM_TABLE_COLUMNS,
Button, Button,
DropdownMenu, DropdownMenu,
MultiSelect,
PageHeader, PageHeader,
Popover, Popover,
SearchInput, SearchInput,
Slider,
Switch,
Text,
TextTitle, TextTitle,
VirtualInfiniteGridRef, VirtualInfiniteGridRef,
} from '/@/renderer/components'; } from '/@/renderer/components';
@ -36,8 +42,10 @@ import {
useCurrentServer, useCurrentServer,
useSetAlbumFilters, useSetAlbumFilters,
useSetAlbumStore, useSetAlbumStore,
useSetAlbumTable,
useSetAlbumTablePagination,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { ListDisplayType } from '/@/renderer/types'; import { ListDisplayType, TableColumn } from '/@/renderer/types';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
@ -82,9 +90,10 @@ const HeaderItems = styled.div`
interface AlbumListHeaderProps { interface AlbumListHeaderProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>; gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
tableRef: MutableRefObject<AgGridReactType | null>;
} }
export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => { export const AlbumListHeader = ({ gridRef, tableRef }: AlbumListHeaderProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const server = useCurrentServer(); const server = useCurrentServer();
const setPage = useSetAlbumStore(); const setPage = useSetAlbumStore();
@ -95,6 +104,9 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
const musicFoldersQuery = useMusicFolders(); const musicFoldersQuery = useMusicFolders();
const setPagination = useSetAlbumTablePagination();
const setTable = useSetAlbumTable();
const sortByLabel = const sortByLabel =
(server?.type && (server?.type &&
FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filters.sortBy)?.name) || FILTERS[server.type as keyof typeof FILTERS].find((f) => f.value === filters.sortBy)?.name) ||
@ -102,13 +114,16 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
const sortOrderLabel = ORDER.find((o) => o.value === filters.sortOrder)?.name || 'Unknown'; const sortOrderLabel = ORDER.find((o) => o.value === filters.sortOrder)?.name || 'Unknown';
const setSize = throttle( const handleItemSize = (e: number) => {
(e: number) => if (
setPage({ page.display === ListDisplayType.TABLE ||
list: { ...page, grid: { ...page.grid, size: e } }, page.display === ListDisplayType.TABLE_PAGINATED
}), ) {
200, setTable({ rowHeight: e });
); } else {
setPage({ list: { ...page, grid: { ...page.grid, size: e } } });
}
};
const fetch = useCallback( const fetch = useCallback(
async (skip: number, take: number, filters: AlbumListFilter) => { async (skip: number, take: number, filters: AlbumListFilter) => {
@ -137,6 +152,46 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
const handleFilterChange = useCallback( const handleFilterChange = useCallback(
async (filters: AlbumListFilter) => { async (filters: AlbumListFilter) => {
if (
page.display === ListDisplayType.TABLE ||
page.display === ListDisplayType.TABLE_PAGINATED
) {
const dataSource: IDatasource = {
getRows: async (params) => {
const limit = params.endRow - params.startRow;
const startIndex = params.startRow;
const queryKey = queryKeys.albums.list(server?.id || '', {
limit,
startIndex,
...filters,
});
const albumsRes = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
api.controller.getAlbumList({
query: {
limit,
startIndex,
...filters,
},
server,
signal,
}),
);
const albums = api.normalize.albumList(albumsRes, server);
params.successCallback(albums?.items || [], albumsRes?.totalRecordCount || undefined);
},
rowCount: undefined,
};
tableRef.current?.api.setDatasource(dataSource);
tableRef.current?.api.purgeInfiniteCache();
tableRef.current?.api.ensureIndexVisible(0, 'top');
if (page.display === ListDisplayType.TABLE_PAGINATED) {
setPagination({ currentPage: 0 });
}
} else {
gridRef.current?.scrollTo(0); gridRef.current?.scrollTo(0);
gridRef.current?.resetLoadMoreItemsCache(); gridRef.current?.resetLoadMoreItemsCache();
@ -147,8 +202,9 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
if (!data?.items) return; if (!data?.items) return;
gridRef.current?.setItemData(data.items); gridRef.current?.setItemData(data.items);
}
}, },
[gridRef, fetch], [page.display, tableRef, setPagination, server, queryClient, gridRef, fetch],
); );
const handleSetSortBy = useCallback( const handleSetSortBy = useCallback(
@ -194,14 +250,7 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
const handleSetViewType = useCallback( const handleSetViewType = useCallback(
(e: MouseEvent<HTMLButtonElement>) => { (e: MouseEvent<HTMLButtonElement>) => {
if (!e.currentTarget?.value) return; if (!e.currentTarget?.value) return;
const type = e.currentTarget.value; setPage({ list: { ...page, display: e.currentTarget.value as ListDisplayType } });
if (type === ListDisplayType.CARD) {
setPage({ list: { ...page, display: ListDisplayType.CARD } });
} else if (type === ListDisplayType.POSTER) {
setPage({ list: { ...page, display: ListDisplayType.POSTER } });
} else {
setPage({ list: { ...page, display: ListDisplayType.TABLE } });
}
}, },
[page, setPage], [page, setPage],
); );
@ -213,6 +262,39 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
if (previousSearchTerm !== searchTerm) handleFilterChange(updatedFilters); if (previousSearchTerm !== searchTerm) handleFilterChange(updatedFilters);
}, 500); }, 500);
const handleTableColumns = (values: TableColumn[]) => {
const existingColumns = page.table.columns;
if (values.length === 0) {
return setTable({
columns: [],
});
}
// If adding a column
if (values.length > existingColumns.length) {
const newColumn = { column: values[values.length - 1], width: 100 };
setTable({ columns: [...existingColumns, newColumn] });
} else {
// If removing a column
const removed = existingColumns.filter((column) => !values.includes(column.column));
const newColumns = existingColumns.filter((column) => !removed.includes(column));
setTable({ columns: newColumns });
}
return tableRef.current?.api.sizeColumnsToFit();
};
const handleAutoFitColumns = (e: ChangeEvent<HTMLInputElement>) => {
setTable({ autoFit: e.currentTarget.checked });
if (e.currentTarget.checked) {
tableRef.current?.api.sizeColumnsToFit();
}
};
return ( return (
<PageHeader> <PageHeader>
<HeaderItems ref={cq.ref}> <HeaderItems ref={cq.ref}>
@ -239,15 +321,6 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
</Button> </Button>
</DropdownMenu.Target> </DropdownMenu.Target>
<DropdownMenu.Dropdown> <DropdownMenu.Dropdown>
<DropdownMenu.Label>Item size</DropdownMenu.Label>
<DropdownMenu.Item>
<Slider
defaultValue={page.grid.size || 0}
label={null}
onChange={setSize}
/>
</DropdownMenu.Item>
<DropdownMenu.Divider />
<DropdownMenu.Label>Display type</DropdownMenu.Label> <DropdownMenu.Label>Display type</DropdownMenu.Label>
<DropdownMenu.Item <DropdownMenu.Item
$isActive={page.display === ListDisplayType.CARD} $isActive={page.display === ListDisplayType.CARD}
@ -264,20 +337,69 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
Poster Poster
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
disabled
$isActive={page.display === ListDisplayType.TABLE} $isActive={page.display === ListDisplayType.TABLE}
value="list" value={ListDisplayType.TABLE}
onClick={handleSetViewType} onClick={handleSetViewType}
> >
List Table
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item
$isActive={page.display === ListDisplayType.TABLE_PAGINATED}
value={ListDisplayType.TABLE_PAGINATED}
onClick={handleSetViewType}
>
Table (paginated)
</DropdownMenu.Item>
<DropdownMenu.Divider />
<DropdownMenu.Label>Item size</DropdownMenu.Label>
<DropdownMenu.Item closeMenuOnClick={false}>
<Slider
defaultValue={
page.display === ListDisplayType.CARD || page.display === ListDisplayType.POSTER
? page.grid.size
: page.table.rowHeight
}
label={null}
max={100}
min={25}
onChangeEnd={handleItemSize}
/>
</DropdownMenu.Item>
{(page.display === ListDisplayType.TABLE ||
page.display === ListDisplayType.TABLE_PAGINATED) && (
<>
<DropdownMenu.Label>Table Columns</DropdownMenu.Label>
<DropdownMenu.Item
closeMenuOnClick={false}
component="div"
sx={{ cursor: 'default' }}
>
<Stack>
<MultiSelect
clearable
data={ALBUM_TABLE_COLUMNS}
defaultValue={page.table?.columns.map((column) => column.column)}
width={300}
onChange={handleTableColumns}
/>
<Group position="apart">
<Text>Auto Fit Columns</Text>
<Switch
defaultChecked={page.table.autoFit}
onChange={handleAutoFitColumns}
/>
</Group>
</Stack>
</DropdownMenu.Item>
</>
)}
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
<DropdownMenu position="bottom-start"> <DropdownMenu position="bottom-start">
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact compact
fw="normal" fw="600"
variant="subtle" variant="subtle"
> >
{sortByLabel} {sortByLabel}
@ -298,8 +420,7 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
</DropdownMenu> </DropdownMenu>
<Button <Button
compact compact
fw="normal" fw="600"
tooltip={!cq.isMd ? { label: sortOrderLabel } : undefined}
variant="subtle" variant="subtle"
onClick={handleToggleSortOrder} onClick={handleToggleSortOrder}
> >
@ -320,8 +441,7 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact compact
fw="normal" fw="600"
tooltip={!cq.isMd ? { label: 'Folder' } : undefined}
variant="subtle" variant="subtle"
> >
{cq.isMd ? 'Folder' : <RiFolder2Line size={15} />} {cq.isMd ? 'Folder' : <RiFolder2Line size={15} />}
@ -341,15 +461,11 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
</DropdownMenu.Dropdown> </DropdownMenu.Dropdown>
</DropdownMenu> </DropdownMenu>
)} )}
<Popover <Popover position="bottom-start">
closeOnClickOutside={false}
position="bottom-start"
>
<Popover.Target> <Popover.Target>
<Button <Button
compact compact
fw="normal" fw="600"
tooltip={!cq.isMd ? { label: 'Filters' } : undefined}
variant="subtle" variant="subtle"
> >
{cq.isMd ? 'Filters' : <RiFilter3Line size={15} />} {cq.isMd ? 'Filters' : <RiFilter3Line size={15} />}
@ -367,7 +483,6 @@ export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => {
<DropdownMenu.Target> <DropdownMenu.Target>
<Button <Button
compact compact
tooltip={{ label: 'More' }}
variant="subtle" variant="subtle"
> >
<RiMoreFill size={15} /> <RiMoreFill size={15} />

View file

@ -3,15 +3,23 @@ import { AnimatedPage } from '/@/renderer/features/shared';
import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header'; import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header';
import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content'; import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content';
import { useRef } from 'react'; import { useRef } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
const AlbumListRoute = () => { const AlbumListRoute = () => {
const gridRef = useRef<VirtualInfiniteGridRef | null>(null); const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
const tableRef = useRef<AgGridReactType | null>(null);
return ( return (
<AnimatedPage> <AnimatedPage>
<VirtualGridContainer> <VirtualGridContainer>
<AlbumListHeader gridRef={gridRef} /> <AlbumListHeader
<AlbumListContent gridRef={gridRef} /> gridRef={gridRef}
tableRef={tableRef}
/>
<AlbumListContent
gridRef={gridRef}
tableRef={tableRef}
/>
</VirtualGridContainer> </VirtualGridContainer>
</AnimatedPage> </AnimatedPage>
); );

View file

@ -3,11 +3,13 @@ import create from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { AlbumListArgs, AlbumListSort, SortOrder } from '/@/renderer/api/types'; import { AlbumListArgs, AlbumListSort, SortOrder } from '/@/renderer/api/types';
import { ListDisplayType } from '/@/renderer/types'; import { DataTableProps } from '/@/renderer/store/settings.store';
import { ListDisplayType, TableColumn, TablePagination } from '/@/renderer/types';
type TableProps = { type TableProps = {
pagination: TablePagination;
scrollOffset: number; scrollOffset: number;
}; } & DataTableProps;
type ListProps<T> = { type ListProps<T> = {
display: ListDisplayType; display: ListDisplayType;
@ -29,6 +31,8 @@ export interface AlbumSlice extends AlbumState {
actions: { actions: {
setFilters: (data: Partial<AlbumListFilter>) => AlbumListFilter; setFilters: (data: Partial<AlbumListFilter>) => AlbumListFilter;
setStore: (data: Partial<AlbumSlice>) => void; setStore: (data: Partial<AlbumSlice>) => void;
setTable: (data: Partial<TableProps>) => void;
setTablePagination: (data: Partial<TableProps['pagination']>) => void;
}; };
} }
@ -47,6 +51,16 @@ export const useAlbumStore = create<AlbumSlice>()(
setStore: (data) => { setStore: (data) => {
set({ ...get(), ...data }); set({ ...get(), ...data });
}, },
setTable: (data) => {
set((state) => {
state.list.table = { ...state.list.table, ...data };
});
},
setTablePagination: (data) => {
set((state) => {
state.list.table.pagination = { ...state.list.table.pagination, ...data };
});
},
}, },
list: { list: {
display: ListDisplayType.CARD, display: ListDisplayType.CARD,
@ -60,6 +74,36 @@ export const useAlbumStore = create<AlbumSlice>()(
size: 50, size: 50,
}, },
table: { table: {
autoFit: true,
columns: [
{
column: TableColumn.ROW_INDEX,
width: 50,
},
{
column: TableColumn.TITLE_COMBINED,
width: 500,
},
{
column: TableColumn.DURATION,
width: 100,
},
{
column: TableColumn.ALBUM_ARTIST,
width: 300,
},
{
column: TableColumn.YEAR,
width: 100,
},
],
pagination: {
currentPage: 1,
itemsPerPage: 100,
totalItems: 1,
totalPages: 1,
},
rowHeight: 60,
scrollOffset: 0, scrollOffset: 0,
}, },
}, },
@ -83,3 +127,10 @@ export const useSetAlbumStore = () => useAlbumStore((state) => state.actions.set
export const useSetAlbumFilters = () => useAlbumStore((state) => state.actions.setFilters); export const useSetAlbumFilters = () => useAlbumStore((state) => state.actions.setFilters);
export const useAlbumListStore = () => useAlbumStore((state) => state.list); export const useAlbumListStore = () => useAlbumStore((state) => state.list);
export const useAlbumTablePagination = () => useAlbumStore((state) => state.list.table.pagination);
export const useSetAlbumTablePagination = () =>
useAlbumStore((state) => state.actions.setTablePagination);
export const useSetAlbumTable = () => useAlbumStore((state) => state.actions.setTable);