Add loading skeleton to table cell rows
This commit is contained in:
parent
a147b56485
commit
39a114aad9
6 changed files with 118 additions and 45 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 ? (
|
||||||
|
|
|
@ -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),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Reference in a new issue