Add lazy image component

This commit is contained in:
jeffvli
2022-11-08 01:35:21 -08:00
parent ecb090d324
commit f7ea6c45f5
4 changed files with 70 additions and 204 deletions
+16
View File
@@ -45,6 +45,7 @@
"react-player": "^2.10.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-simple-img": "^3.0.0",
"react-slider": "^2.0.0",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.7",
@@ -17399,6 +17400,15 @@
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-simple-img": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-simple-img/-/react-simple-img-3.0.0.tgz",
"integrity": "sha512-I0sG/GgY9c+04BgWf1YRlipWBQxR3oG2s/bagU8EO7zals3/Vkfk1PJMeYh/wHfjxJtUmal+y7HWEBm4MzXVsQ==",
"peerDependencies": {
"react": ">= 16.3.0",
"react-dom": ">= 16.3.0"
}
},
"node_modules/react-slider": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-slider/-/react-slider-2.0.0.tgz",
@@ -36732,6 +36742,12 @@
"react-is": "^16.12.0 || ^17.0.0 || ^18.0.0"
}
},
"react-simple-img": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-simple-img/-/react-simple-img-3.0.0.tgz",
"integrity": "sha512-I0sG/GgY9c+04BgWf1YRlipWBQxR3oG2s/bagU8EO7zals3/Vkfk1PJMeYh/wHfjxJtUmal+y7HWEBm4MzXVsQ==",
"requires": {}
},
"react-slider": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-slider/-/react-slider-2.0.0.tgz",
+1
View File
@@ -285,6 +285,7 @@
"react-player": "^2.10.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-simple-img": "^3.0.0",
"react-slider": "^2.0.0",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.7",
@@ -3,6 +3,7 @@ import { Center, Skeleton } from '@mantine/core';
import { RiAlbumFill } from 'react-icons/ri';
import { generatePath, useNavigate } from 'react-router';
import { Link } from 'react-router-dom';
import { SimpleImg, initSimpleImg } from 'react-simple-img';
import { ListChildComponentProps } from 'react-window';
import styled from 'styled-components';
import { Text } from '@/renderer/components/text';
@@ -15,6 +16,8 @@ import {
CardRoute,
} from '@/renderer/types';
initSimpleImg({ threshold: 0.5 }, true);
const CardWrapper = styled.div<{
itemGap: number;
itemHeight: number;
@@ -87,14 +90,7 @@ const ImageSection = styled.div<{ size?: number }>`
}
`;
interface ImageProps {
height: number;
isLoading?: boolean;
}
const Image = styled.img<ImageProps>`
width: ${({ height }) => `${height - 24}px`};
height: ${({ height }) => `${height - 24}px`};
const Image = styled(SimpleImg)`
object-fit: cover;
border: 0;
border-radius: var(--card-default-radius);
@@ -127,6 +123,7 @@ const Row = styled.div<{ $secondary?: boolean }>`
$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)'};
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
`;
interface BaseGridCardProps {
@@ -136,7 +133,7 @@ interface BaseGridCardProps {
cardRows: CardRow[];
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemType: LibraryItem;
route?: CardRoute;
route: CardRoute;
};
data: any;
listChildProps: Omit<ListChildComponentProps, 'data' | 'style'>;
@@ -161,161 +158,37 @@ export const DefaultCard = ({
controls;
if (data) {
if (route) {
return (
<CardWrapper
key={`card-${columnIndex}-${index}`}
link
itemGap={itemGap}
itemHeight={itemHeight}
itemWidth={itemWidth}
onClick={() =>
navigate(
generatePath(
route.route,
route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {})
)
)
}
>
<StyledCard>
<ImageSection size={itemWidth}>
{data?.imageUrl ? (
<Image height={itemWidth} src={data?.imageUrl} />
) : (
<Center
sx={{
background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)',
height: '100%',
width: '100%',
}}
>
<RiAlbumFill color="var(--placeholder-fg)" size={35} />
</Center>
)}
<ControlsContainer>
{!isScrolling && (
<GridCardControls
cardControls={cardControls}
handlePlayQueueAdd={handlePlayQueueAdd}
itemData={data}
itemType={itemType}
/>
)}
</ControlsContainer>
</ImageSection>
<DetailSection>
{cardRows.map((row: CardRow, index: number) => {
if (row.arrayProperty && row.route) {
return (
<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
sx={{
display: 'inline-block',
padding: '0 2px 0 1px',
}}
>
,
</Text>
)}{' '}
<Text
$link
$secondary={index > 0}
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()}
>
{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}`}
$secondary={index > 0}
overflow="hidden"
>
{row.arrayProperty && item[row.arrayProperty]}
</Text>
))}
</Row>
);
}
return (
<Row key={`row-${row.property}-${columnIndex}`}>
{row.route ? (
<Text
$link
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 $secondary={index > 0} overflow="hidden">
{data && data[row.property]}
</Text>
)}
</Row>
);
})}
</DetailSection>
</StyledCard>
</CardWrapper>
);
}
return (
<CardWrapper
key={`card-${columnIndex}-${index}`}
link
itemGap={itemGap}
itemHeight={itemHeight}
itemWidth={itemWidth}
onClick={() =>
navigate(
generatePath(
route.route,
route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {})
)
)
}
>
<StyledCard>
<ImageSection size={itemWidth}>
{data?.imageUrl ? (
<Image height={itemWidth} src={data?.imageUrl} />
<Image
animationDuration={0.5}
height={itemWidth - 24}
placeholder="var(--card-default-bg)"
src={data?.imageUrl}
width={itemWidth - 24}
/>
) : (
<Center
sx={{
@@ -3,11 +3,11 @@ import { Center, Skeleton } from '@mantine/core';
import { RiAlbumFill } from 'react-icons/ri';
import { generatePath } from 'react-router';
import { Link } from 'react-router-dom';
import { SimpleImg, initSimpleImg } from 'react-simple-img';
import { ListChildComponentProps } from 'react-window';
import styled from 'styled-components';
import { Text } from '@/renderer/components/text';
import { GridCardControls } from '@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { fadeIn } from '@/renderer/styles';
import {
PlayQueueAddOptions,
LibraryItem,
@@ -15,6 +15,8 @@ import {
CardRoute,
} from '@/renderer/types';
initSimpleImg({ threshold: 0.5 }, true);
const CardWrapper = styled.div<{
itemGap: number;
itemHeight: number;
@@ -87,15 +89,10 @@ interface ImageProps {
isLoading?: boolean;
}
const Image = styled.img<ImageProps>`
width: ${({ height }) => `${height}px`};
height: ${({ height }) => `${height}px`};
const Image = styled(SimpleImg)<ImageProps>`
object-fit: cover;
border: 0;
border-radius: var(--card-poster-radius);
${fadeIn}
animation: fadein 0.3s ease-in-out;
`;
const ControlsContainer = styled.div`
@@ -122,6 +119,7 @@ const Row = styled.div<{ $secondary?: boolean }>`
$secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)'};
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
`;
interface BaseGridCardProps {
@@ -131,7 +129,7 @@ interface BaseGridCardProps {
cardRows: CardRow[];
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemType: LibraryItem;
route?: CardRoute;
route: CardRoute;
};
data: any;
listChildProps: Omit<ListChildComponentProps, 'data' | 'style'>;
@@ -163,49 +161,27 @@ export const PosterCard = ({
itemWidth={itemWidth}
>
<StyledCard>
{route ? (
<Link
tabIndex={0}
to={generatePath(
route.route,
route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {})
)}
>
<ImageSection style={{ height: `${itemWidth}px` }}>
{data?.imageUrl ? (
<Image height={itemWidth} src={data?.imageUrl} />
) : (
<Center
sx={{
background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-poster-radius)',
height: '100%',
}}
>
<RiAlbumFill color="var(--placeholder-fg)" size={35} />
</Center>
)}
<ControlsContainer>
{!isScrolling && (
<GridCardControls
cardControls={cardControls}
handlePlayQueueAdd={handlePlayQueueAdd}
itemData={data}
itemType={itemType}
/>
)}
</ControlsContainer>
</ImageSection>
</Link>
) : (
<Link
tabIndex={0}
to={generatePath(
route.route,
route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {})
)}
>
<ImageSection style={{ height: `${itemWidth}px` }}>
{data?.imageUrl ? (
<Image height={itemWidth} src={data?.imageUrl} />
<Image
animationDuration={0.5}
height={itemWidth}
placeholder="var(--card-default-bg)"
src={data?.imageUrl}
width={itemWidth}
/>
) : (
<Center
sx={{
@@ -228,7 +204,7 @@ export const PosterCard = ({
)}
</ControlsContainer>
</ImageSection>
)}
</Link>
<DetailSection>
{cardRows.map((row: CardRow, index: number) => {
if (row.arrayProperty && row.route) {