Add loading skeleton to table cell rows

This commit is contained in:
jeffvli 2022-12-20 04:25:51 -08:00
parent a147b56485
commit 39a114aad9
6 changed files with 118 additions and 45 deletions

View file

@ -6,8 +6,20 @@ import type { AlbumArtist, Artist } from '/@/renderer/api/types';
import { Text } from '/@/renderer/components/text'; import { Text } from '/@/renderer/components/text';
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell'; import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { Skeleton } from '/@/renderer/components/skeleton';
export const AlbumArtistCell = ({ value, data }: ICellRendererParams) => { export const AlbumArtistCell = ({ value, data }: ICellRendererParams) => {
if (!value) {
return (
<CellContainer position="left">
<Skeleton
height="1rem"
width="80%"
/>
</CellContainer>
);
}
return ( return (
<CellContainer position="left"> <CellContainer position="left">
<Text <Text

View file

@ -6,8 +6,20 @@ import type { AlbumArtist, Artist } from '/@/renderer/api/types';
import { Text } from '/@/renderer/components/text'; import { Text } from '/@/renderer/components/text';
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell'; import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { Skeleton } from '/@/renderer/components/skeleton';
export const ArtistCell = ({ value, data }: ICellRendererParams) => { export const ArtistCell = ({ value, data }: ICellRendererParams) => {
if (!value) {
return (
<CellContainer position="left">
<Skeleton
height="1rem"
width="80%"
/>
</CellContainer>
);
}
return ( return (
<CellContainer position="left"> <CellContainer position="left">
<Text <Text

View file

@ -8,6 +8,7 @@ import type { AlbumArtist, Artist } from '/@/renderer/api/types';
import { Text } from '/@/renderer/components/text'; import { Text } from '/@/renderer/components/text';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { ServerType } from '/@/renderer/types'; import { ServerType } from '/@/renderer/types';
import { Skeleton } from '/@/renderer/components/skeleton';
const CellContainer = styled(motion.div)<{ height: number }>` const CellContainer = styled(motion.div)<{ height: number }>`
display: grid; display: grid;
@ -43,16 +44,37 @@ const StyledImage = styled.img`
export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams) => { export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams) => {
const artists = useMemo(() => { const artists = useMemo(() => {
return value.type === ServerType.JELLYFIN ? value.artists : value.albumArtists; if (!value) return null;
return value?.type === ServerType.JELLYFIN ? value.artists : value.albumArtists;
}, [value]); }, [value]);
if (!value) {
return (
<CellContainer height={node.rowHeight || 40}>
<Skeleton>
<ImageWrapper />
</Skeleton>
<MetadataWrapper>
<Skeleton
height="1rem"
width="80%"
/>
<Skeleton
height="1rem"
mt="0.5rem"
width="60%"
/>
</MetadataWrapper>
</CellContainer>
);
}
return ( return (
<CellContainer height={node.rowHeight || 40}> <CellContainer height={node.rowHeight || 40}>
<ImageWrapper> <ImageWrapper>
<StyledImage <StyledImage
alt="song-cover" alt="cover"
height={(node.rowHeight || 40) - 10} height={(node.rowHeight || 40) - 10}
loading="lazy"
src={value.imageUrl} src={value.imageUrl}
style={{}} style={{}}
width={(node.rowHeight || 40) - 10} width={(node.rowHeight || 40) - 10}

View file

@ -1,6 +1,7 @@
import type { ICellRendererParams } from '@ag-grid-community/core'; import type { ICellRendererParams } from '@ag-grid-community/core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { Skeleton } from '/@/renderer/components/skeleton';
import { Text } from '/@/renderer/components/text'; import { Text } from '/@/renderer/components/text';
export const CellContainer = styled.div<{ export const CellContainer = styled.div<{
@ -32,6 +33,17 @@ export const GenericCell = (
) => { ) => {
const displayedValue = valueFormatted || value; const displayedValue = valueFormatted || value;
if (!value) {
return (
<CellContainer position={position || 'left'}>
<Skeleton
height="1rem"
width="80%"
/>
</CellContainer>
);
}
return ( return (
<CellContainer position={position || 'left'}> <CellContainer position={position || 'left'}>
{isLink ? ( {isLink ? (

View file

@ -39,24 +39,28 @@ const tableColumns: { [key: string]: ColDef } = {
GenericCell(params, { isLink: true, position: 'left' }), GenericCell(params, { isLink: true, position: 'left' }),
colId: TableColumn.ALBUM, colId: TableColumn.ALBUM,
headerName: 'Album', headerName: 'Album',
valueGetter: (params: ValueGetterParams) => ({ valueGetter: (params: ValueGetterParams) =>
link: generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { params.data
albumId: params.data?.albumId || '', ? {
}), link: generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
value: params.data?.album, albumId: params.data?.albumId || '',
}), }),
value: params.data?.album,
}
: undefined,
}, },
albumArtist: { albumArtist: {
cellRenderer: AlbumArtistCell, cellRenderer: AlbumArtistCell,
colId: TableColumn.ALBUM_ARTIST, colId: TableColumn.ALBUM_ARTIST,
headerName: 'Album Artist', headerName: 'Album Artist',
valueGetter: (params: ValueGetterParams) => params.data?.albumArtists, valueGetter: (params: ValueGetterParams) =>
params.data ? params.data.albumArtists : undefined,
}, },
artist: { artist: {
cellRenderer: ArtistCell, cellRenderer: ArtistCell,
colId: TableColumn.ARTIST, colId: TableColumn.ARTIST,
headerName: 'Artist', headerName: 'Artist',
valueGetter: (params: ValueGetterParams) => params.data?.artists, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.artists : undefined),
}, },
bitRate: { bitRate: {
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }), cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }),
@ -64,6 +68,7 @@ const tableColumns: { [key: string]: ColDef } = {
field: 'bitRate', field: 'bitRate',
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }), headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }),
valueFormatter: (params: ValueFormatterParams) => `${params.value} kbps`, valueFormatter: (params: ValueFormatterParams) => `${params.value} kbps`,
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.bitRate : undefined),
}, },
dateAdded: { dateAdded: {
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }), cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }),
@ -72,6 +77,7 @@ const tableColumns: { [key: string]: ColDef } = {
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }), headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }),
headerName: 'Date Added', headerName: 'Date Added',
valueFormatter: (params: ValueFormatterParams) => params.value?.split('T')[0], valueFormatter: (params: ValueFormatterParams) => params.value?.split('T')[0],
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.createdAt : undefined),
}, },
discNumber: { discNumber: {
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
@ -81,6 +87,7 @@ const tableColumns: { [key: string]: ColDef } = {
headerName: 'Disc', headerName: 'Disc',
initialWidth: 75, initialWidth: 75,
suppressSizeToFit: true, suppressSizeToFit: true,
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.discNumber : undefined),
}, },
duration: { duration: {
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
@ -90,12 +97,13 @@ const tableColumns: { [key: string]: ColDef } = {
GenericTableHeader(params, { position: 'center', preset: 'duration' }), GenericTableHeader(params, { position: 'center', preset: 'duration' }),
initialWidth: 100, initialWidth: 100,
valueFormatter: (params: ValueFormatterParams) => formatDuration(params.value * 1000), valueFormatter: (params: ValueFormatterParams) => formatDuration(params.value * 1000),
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.duration : undefined),
}, },
genre: { genre: {
cellRenderer: GenreCell, cellRenderer: GenreCell,
colId: TableColumn.GENRE, colId: TableColumn.GENRE,
headerName: 'Genre', headerName: 'Genre',
valueGetter: (params: ValueGetterParams) => params.data.genres, valueGetter: (params: ValueGetterParams) => (params.data ? params.data.genres : undefined),
}, },
releaseDate: { releaseDate: {
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
@ -104,6 +112,7 @@ const tableColumns: { [key: string]: ColDef } = {
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
headerName: 'Release Date', headerName: 'Release Date',
valueFormatter: (params: ValueFormatterParams) => params.value?.split('T')[0], valueFormatter: (params: ValueFormatterParams) => params.value?.split('T')[0],
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.releaseDate : undefined),
}, },
releaseYear: { releaseYear: {
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
@ -111,6 +120,7 @@ const tableColumns: { [key: string]: ColDef } = {
field: 'releaseYear', field: 'releaseYear',
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }), headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
headerName: 'Year', headerName: 'Year',
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.releaseYear : undefined),
}, },
rowIndex: { rowIndex: {
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }), cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }),
@ -129,20 +139,24 @@ const tableColumns: { [key: string]: ColDef } = {
colId: TableColumn.TITLE, colId: TableColumn.TITLE,
field: 'name', field: 'name',
headerName: 'Title', headerName: 'Title',
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.name : undefined),
}, },
titleCombined: { titleCombined: {
cellRenderer: CombinedTitleCell, cellRenderer: CombinedTitleCell,
colId: TableColumn.TITLE_COMBINED, colId: TableColumn.TITLE_COMBINED,
headerName: 'Title', headerName: 'Title',
initialWidth: 500, initialWidth: 500,
valueGetter: (params: ValueGetterParams) => ({ valueGetter: (params: ValueGetterParams) =>
albumArtists: params.data?.albumArtists, params.data
artists: params.data?.artists, ? {
imageUrl: params.data?.imageUrl, albumArtists: params.data?.albumArtists,
name: params.data?.name, artists: params.data?.artists,
rowHeight: params.node?.rowHeight, imageUrl: params.data?.imageUrl,
type: params.data?.type, name: params.data?.name,
}), rowHeight: params.node?.rowHeight,
type: params.data?.type,
}
: undefined,
}, },
trackNumber: { trackNumber: {
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
@ -152,6 +166,7 @@ const tableColumns: { [key: string]: ColDef } = {
headerName: 'Track', headerName: 'Track',
initialWidth: 75, initialWidth: 75,
suppressSizeToFit: true, suppressSizeToFit: true,
valueGetter: (params: ValueGetterParams) => (params.data ? params.data.trackNumber : undefined),
}, },
}; };

View file

@ -44,7 +44,6 @@ const TrackListRoute = () => {
(params: GridReadyEvent) => { (params: GridReadyEvent) => {
const dataSource: IDatasource = { const dataSource: IDatasource = {
getRows: async (params) => { getRows: async (params) => {
console.log(`asking for ${params.startRow} to ${params.endRow}`);
const limit = params.endRow - params.startRow; const limit = params.endRow - params.startRow;
const startIndex = params.startRow; const startIndex = params.startRow;
@ -69,7 +68,6 @@ const TrackListRoute = () => {
const songs = api.normalize.songList(songsRes, server); const songs = api.normalize.songList(songsRes, server);
console.log('songs :>> ', songs);
params.successCallback(songs?.items || [], -1); params.successCallback(songs?.items || [], -1);
}, },
rowCount: undefined, rowCount: undefined,
@ -84,29 +82,31 @@ const TrackListRoute = () => {
<VirtualGridContainer> <VirtualGridContainer>
<SongListHeader /> <SongListHeader />
<VirtualGridAutoSizerContainer> <VirtualGridAutoSizerContainer>
<VirtualTable {!checkSongList.isLoading && (
alwaysShowHorizontalScroll <VirtualTable
animateRows alwaysShowHorizontalScroll
maintainColumnOrder animateRows
suppressCopyRowsToClipboard maintainColumnOrder
suppressMoveWhenRowDragging suppressCopyRowsToClipboard
suppressRowDrag suppressMoveWhenRowDragging
suppressScrollOnNewData suppressRowDrag
cacheBlockSize={500} suppressScrollOnNewData
cacheOverflowSize={0} blockLoadDebounceMillis={200}
columnDefs={columnDefs} cacheBlockSize={500}
defaultColDef={defaultColumnDefs} cacheOverflowSize={1}
enableCellChangeFlash={false} columnDefs={columnDefs}
getRowId={(data) => data.data.uniqueId} defaultColDef={defaultColumnDefs}
infiniteInitialRowCount={checkSongList.data?.totalRecordCount} enableCellChangeFlash={false}
maxConcurrentDatasourceRequests={3} getRowId={(data) => data.data.uniqueId}
rowBuffer={20} infiniteInitialRowCount={checkSongList.data?.totalRecordCount}
// rowData={queue} rowBuffer={20}
rowHeight={tableConfig.rowHeight || 40} rowHeight={tableConfig.rowHeight || 40}
rowModelType="infinite" rowModelType="infinite"
rowSelection="multiple" rowSelection="multiple"
onGridReady={onGridReady} onCellContextMenu={(e) => console.log('context', e)}
/> onGridReady={onGridReady}
/>
)}
</VirtualGridAutoSizerContainer> </VirtualGridAutoSizerContainer>
</VirtualGridContainer> </VirtualGridContainer>
</AnimatedPage> </AnimatedPage>