Move card rows to separate component

This commit is contained in:
jeffvli 2022-12-24 13:44:52 -08:00
parent 6eb08243b7
commit 7d1083d1f7
5 changed files with 201 additions and 236 deletions

View file

@ -0,0 +1,180 @@
import React from 'react';
import { generatePath } from 'react-router';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { Album, AlbumArtist, Artist } from '/@/renderer/api/types';
import { Text } from '/@/renderer/components/text';
import { AppRoute } from '/@/renderer/router/routes';
import { CardRow } from '/@/renderer/types';
const Row = styled.div<{ $secondary?: boolean }>`
width: 100%;
max-width: 100%;
height: 22px;
padding: 0 0.2rem;
overflow: hidden;
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
`;
interface CardRowsProps {
data: any;
rows: CardRow<Album | Artist | AlbumArtist>[];
}
export const CardRows = ({ data, rows }: CardRowsProps) => {
return (
<>
{rows.map((row, index: number) => {
if (row.arrayProperty && row.route) {
return (
<Row
key={`row-${row.property}-${index}`}
$secondary={index > 0}
>
{data[row.property].map((item: any, itemIndex: number) => (
<React.Fragment key={`${data.id}-${item.id}`}>
{itemIndex > 0 && (
<Text
$noSelect
sx={{
display: 'inline-block',
padding: '0 2px 0 1px',
}}
>
,
</Text>
)}{' '}
<Text
$link
$noSelect
$secondary={index > 0}
component={Link}
overflow="hidden"
size={index > 0 ? 'sm' : 'md'}
to={generatePath(
row.route!.route,
row.route!.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
)}
onClick={(e) => e.stopPropagation()}
>
{row.arrayProperty && item[row.arrayProperty]}
</Text>
</React.Fragment>
))}
</Row>
);
}
if (row.arrayProperty) {
return (
<Row key={`row-${row.property}`}>
{data[row.property].map((item: any) => (
<Text
key={`${data.id}-${item.id}`}
$noSelect
$secondary={index > 0}
overflow="hidden"
size={index > 0 ? 'sm' : 'md'}
>
{row.arrayProperty && item[row.arrayProperty]}
</Text>
))}
</Row>
);
}
return (
<Row key={`row-${row.property}`}>
{row.route ? (
<Text
$link
$noSelect
component={Link}
overflow="hidden"
to={generatePath(
row.route.route,
row.route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
)}
onClick={(e) => e.stopPropagation()}
>
{data && data[row.property]}
</Text>
) : (
<Text
$noSelect
$secondary={index > 0}
overflow="hidden"
size={index > 0 ? 'sm' : 'md'}
>
{data && data[row.property]}
</Text>
)}
</Row>
);
})}
</>
);
};
export const ALBUM_CARD_ROWS: { [key: string]: CardRow<Album> } = {
albumArtists: {
arrayProperty: 'name',
property: 'albumArtists',
route: {
route: AppRoute.LIBRARY_ALBUMARTISTS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
},
},
artists: {
arrayProperty: 'name',
property: 'artists',
route: {
route: AppRoute.LIBRARY_ALBUMARTISTS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
},
},
createdAt: {
property: 'createdAt',
},
duration: {
property: 'duration',
},
lastPlayedAt: {
property: 'lastPlayedAt',
},
name: {
property: 'name',
route: {
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
},
},
playCount: {
property: 'playCount',
},
rating: {
property: 'rating',
},
releaseDate: {
property: 'releaseDate',
},
releaseYear: {
property: 'releaseYear',
},
songCount: {
property: 'songCount',
},
};

View file

@ -1 +1,2 @@
export * from './album-card'; export * from './album-card';
export * from './card-rows';

View file

@ -1,15 +1,14 @@
import React from 'react';
import { Center } from '@mantine/core'; import { Center } from '@mantine/core';
import { RiAlbumFill } from 'react-icons/ri'; import { RiAlbumFill } from 'react-icons/ri';
import { generatePath, useNavigate } from 'react-router'; import { generatePath, useNavigate } from 'react-router';
import { Link } from 'react-router-dom';
import { SimpleImg } from 'react-simple-img'; import { SimpleImg } from 'react-simple-img';
import type { ListChildComponentProps } from 'react-window'; import type { ListChildComponentProps } from 'react-window';
import styled from 'styled-components'; import styled from 'styled-components';
import { Text } from '/@/renderer/components/text';
import type { LibraryItem, CardRow, CardRoute, Play, PlayQueueAddOptions } from '/@/renderer/types'; import type { LibraryItem, CardRow, CardRoute, Play, PlayQueueAddOptions } from '/@/renderer/types';
import { Skeleton } from '/@/renderer/components/skeleton'; import { Skeleton } from '/@/renderer/components/skeleton';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls'; import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { Album, AlbumArtist, Artist } from '/@/renderer/api/types';
import { CardRows } from '/@/renderer/components/card';
const CardWrapper = styled.div<{ const CardWrapper = styled.div<{
itemGap: number; itemGap: number;
@ -98,22 +97,10 @@ const DetailSection = styled.div`
flex-direction: column; flex-direction: column;
`; `;
const Row = styled.div<{ $secondary?: boolean }>`
width: 100%;
max-width: 100%;
height: 22px;
padding: 0 0.2rem;
overflow: hidden;
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
`;
interface BaseGridCardProps { interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
controls: { controls: {
cardRows: CardRow[]; cardRows: CardRow<Album | AlbumArtist | Artist>[];
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemType: LibraryItem; itemType: LibraryItem;
playButtonBehavior: Play; playButtonBehavior: Play;
@ -199,104 +186,10 @@ export const DefaultCard = ({
</ControlsContainer> </ControlsContainer>
</ImageSection> </ImageSection>
<DetailSection> <DetailSection>
{cardRows.map((row: CardRow, index: number) => { <CardRows
if (row.arrayProperty && row.route) { data={data}
return ( rows={cardRows}
<Row />
key={`row-${row.property}-${columnIndex}`}
$secondary={index > 0}
>
{data[row.property].map((item: any, itemIndex: number) => (
<React.Fragment key={`${data.id}-${item.id}`}>
{itemIndex > 0 && (
<Text
$noSelect
sx={{
display: 'inline-block',
padding: '0 2px 0 1px',
}}
>
,
</Text>
)}{' '}
<Text
$link
$noSelect
$secondary={index > 0}
component={Link}
overflow="hidden"
size={index > 0 ? 'xs' : 'md'}
to={generatePath(
row.route!.route,
row.route!.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
)}
onClick={(e) => e.stopPropagation()}
>
{row.arrayProperty && item[row.arrayProperty]}
</Text>
</React.Fragment>
))}
</Row>
);
}
if (row.arrayProperty) {
return (
<Row key={`row-${row.property}-${columnIndex}`}>
{data[row.property].map((item: any) => (
<Text
key={`${data.id}-${item.id}`}
$noSelect
$secondary={index > 0}
overflow="hidden"
size={index > 0 ? 'xs' : 'md'}
>
{row.arrayProperty && item[row.arrayProperty]}
</Text>
))}
</Row>
);
}
return (
<Row key={`row-${row.property}-${columnIndex}`}>
{row.route ? (
<Text
$link
$noSelect
component={Link}
overflow="hidden"
to={generatePath(
row.route.route,
row.route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
)}
onClick={(e) => e.stopPropagation()}
>
{data && data[row.property]}
</Text>
) : (
<Text
$noSelect
$secondary={index > 0}
overflow="hidden"
size={index > 0 ? 'xs' : 'md'}
>
{data && data[row.property]}
</Text>
)}
</Row>
);
})}
</DetailSection> </DetailSection>
</StyledCard> </StyledCard>
</CardWrapper> </CardWrapper>
@ -318,7 +211,7 @@ export const DefaultCard = ({
<ImageSection size={itemWidth} /> <ImageSection size={itemWidth} />
</Skeleton> </Skeleton>
<DetailSection> <DetailSection>
{cardRows.map((row: CardRow, index: number) => ( {cardRows.map((row: CardRow<Album | Artist | AlbumArtist>, index: number) => (
<Skeleton <Skeleton
key={`row-${row.property}-${columnIndex}`} key={`row-${row.property}-${columnIndex}`}
height={20} height={20}
@ -326,9 +219,7 @@ export const DefaultCard = ({
radius="md" radius="md"
visible={!data} visible={!data}
width={!data ? (index > 0 ? '50%' : '90%') : '100%'} width={!data ? (index > 0 ? '50%' : '90%') : '100%'}
> />
<Row />
</Skeleton>
))} ))}
</DetailSection> </DetailSection>
</StyledCard> </StyledCard>

View file

@ -1,4 +1,3 @@
import React from 'react';
import { Center } from '@mantine/core'; import { Center } from '@mantine/core';
import { RiAlbumFill } from 'react-icons/ri'; import { RiAlbumFill } from 'react-icons/ri';
import { generatePath } from 'react-router'; import { generatePath } from 'react-router';
@ -7,9 +6,10 @@ import { SimpleImg } from 'react-simple-img';
import type { ListChildComponentProps } from 'react-window'; import type { ListChildComponentProps } from 'react-window';
import styled from 'styled-components'; import styled from 'styled-components';
import { Skeleton } from '/@/renderer/components/skeleton'; import { Skeleton } from '/@/renderer/components/skeleton';
import { Text } from '/@/renderer/components/text';
import type { LibraryItem, CardRow, CardRoute, Play, PlayQueueAddOptions } from '/@/renderer/types'; import type { LibraryItem, CardRow, CardRoute, Play, PlayQueueAddOptions } from '/@/renderer/types';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls'; import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { Album, Artist, AlbumArtist } from '/@/renderer/api/types';
import { CardRows } from '/@/renderer/components/card';
const CardWrapper = styled.div<{ const CardWrapper = styled.div<{
itemGap: number; itemGap: number;
@ -101,22 +101,10 @@ const DetailSection = styled.div`
flex-direction: column; flex-direction: column;
`; `;
const Row = styled.div<{ $secondary?: boolean }>`
width: 100%;
max-width: 100%;
height: 22px;
padding: 0 0.2rem;
overflow: hidden;
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
`;
interface BaseGridCardProps { interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
controls: { controls: {
cardRows: CardRow[]; cardRows: CardRow<Album | AlbumArtist | Artist>[];
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemType: LibraryItem; itemType: LibraryItem;
playButtonBehavior: Play; playButtonBehavior: Play;
@ -193,104 +181,10 @@ export const PosterCard = ({
</ImageSection> </ImageSection>
</Link> </Link>
<DetailSection> <DetailSection>
{controls.cardRows.map((row: CardRow, index: number) => { <CardRows
if (row.arrayProperty && row.route) { data={data}
return ( rows={controls.cardRows}
<Row />
key={`row-${row.property}-${columnIndex}`}
$secondary={index > 0}
>
{data[row.property].map((item: any, itemIndex: number) => (
<React.Fragment key={`${data.id}-${item.id}`}>
{itemIndex > 0 && (
<Text
$noSelect
size={index > 0 ? 'xs' : 'md'}
sx={{
display: 'inline-block',
padding: '0 2px 0 1px',
}}
>
,
</Text>
)}{' '}
<Text
$link
$noSelect
$secondary={index > 0}
component={Link}
overflow="hidden"
size={index > 0 ? 'xs' : 'md'}
to={generatePath(
row.route!.route,
row.route!.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
)}
>
{row.arrayProperty && item[row.arrayProperty]}
</Text>
</React.Fragment>
))}
</Row>
);
}
if (row.arrayProperty) {
return (
<Row key={`row-${row.property}-${columnIndex}`}>
{data[row.property].map((item: any) => (
<Text
key={`${data.id}-${item.id}`}
$noSelect
$secondary={index > 0}
overflow="hidden"
size={index > 0 ? 'xs' : 'md'}
>
{row.arrayProperty && item[row.arrayProperty]}
</Text>
))}
</Row>
);
}
return (
<Row key={`row-${row.property}-${columnIndex}`}>
{row.route ? (
<Text
$link
$noSelect
component={Link}
overflow="hidden"
size={index > 0 ? 'xs' : 'md'}
to={generatePath(
row.route.route,
row.route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
)}
>
{data && data[row.property]}
</Text>
) : (
<Text
$noSelect
$secondary={index > 0}
overflow="hidden"
size={index > 0 ? 'xs' : 'md'}
>
{data && data[row.property]}
</Text>
)}
</Row>
);
})}
</DetailSection> </DetailSection>
</StyledCard> </StyledCard>
</CardWrapper> </CardWrapper>
@ -312,7 +206,7 @@ export const PosterCard = ({
<ImageSection style={{ height: `${sizes.itemWidth}px` }} /> <ImageSection style={{ height: `${sizes.itemWidth}px` }} />
</Skeleton> </Skeleton>
<DetailSection> <DetailSection>
{controls.cardRows.map((row: CardRow, index: number) => ( {controls.cardRows.map((row: CardRow<Album | Artist | AlbumArtist>, index: number) => (
<Skeleton <Skeleton
key={`row-${row.property}-${columnIndex}`} key={`row-${row.property}-${columnIndex}`}
height={20} height={20}
@ -320,9 +214,7 @@ export const PosterCard = ({
radius="md" radius="md"
visible={!data} visible={!data}
width={!data ? (index > 0 ? '50%' : '90%') : '100%'} width={!data ? (index > 0 ? '50%' : '90%') : '100%'}
> />
<Row />
</Skeleton>
))} ))}
</DetailSection> </DetailSection>
</StyledCard> </StyledCard>

View file

@ -12,6 +12,7 @@ import type {
CardRoute, CardRoute,
PlayQueueAddOptions, PlayQueueAddOptions,
} from '/@/renderer/types'; } from '/@/renderer/types';
import { Album, AlbumArtist, Artist } from '/@/renderer/api/types';
const createItemData = memoize( const createItemData = memoize(
( (
@ -61,7 +62,7 @@ export const VirtualGridWrapper = ({
onScroll, onScroll,
...rest ...rest
}: Omit<FixedSizeListProps, 'ref' | 'itemSize' | 'children'> & { }: Omit<FixedSizeListProps, 'ref' | 'itemSize' | 'children'> & {
cardRows: CardRow[]; cardRows: CardRow<Album | AlbumArtist | Artist>[];
columnCount: number; columnCount: number;
display: CardDisplayType; display: CardDisplayType;
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;