mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-16 08:24:16 +02:00
improve image placeholders and loading
This commit is contained in:
@@ -56,9 +56,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.image-container.is-round {
|
.image-container.is-round {
|
||||||
&::before {
|
border-radius: 50%;
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-badge {
|
.favorite-badge {
|
||||||
|
|||||||
@@ -785,9 +785,9 @@ const PosterItemCard = ({
|
|||||||
<>
|
<>
|
||||||
<ItemImage
|
<ItemImage
|
||||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||||
id={data?.id}
|
id={(data as { imageId: string })?.imageId}
|
||||||
itemType={itemType}
|
itemType={itemType}
|
||||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
src={(data as { imageUrl: string })?.imageUrl}
|
||||||
/>
|
/>
|
||||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||||
|
|||||||
@@ -11,6 +11,25 @@ import {
|
|||||||
import { BaseImage, ImageProps } from '/@/shared/components/image/image';
|
import { BaseImage, ImageProps } from '/@/shared/components/image/image';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
const getUnloaderIcon = (itemType: LibraryItem) => {
|
||||||
|
switch (itemType) {
|
||||||
|
case LibraryItem.ALBUM:
|
||||||
|
return 'emptyAlbumImage';
|
||||||
|
case LibraryItem.ALBUM_ARTIST:
|
||||||
|
return 'emptyArtistImage';
|
||||||
|
case LibraryItem.ARTIST:
|
||||||
|
return 'emptyArtistImage';
|
||||||
|
case LibraryItem.GENRE:
|
||||||
|
return 'emptyGenreImage';
|
||||||
|
case LibraryItem.PLAYLIST:
|
||||||
|
return 'emptyPlaylistImage';
|
||||||
|
case LibraryItem.SONG:
|
||||||
|
return 'emptySongImage';
|
||||||
|
default:
|
||||||
|
return 'emptyImage';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const BaseItemImage = (
|
const BaseItemImage = (
|
||||||
props: Omit<ImageProps, 'src'> & {
|
props: Omit<ImageProps, 'src'> & {
|
||||||
id?: null | string;
|
id?: null | string;
|
||||||
@@ -27,7 +46,7 @@ const BaseItemImage = (
|
|||||||
size: 300,
|
size: 300,
|
||||||
});
|
});
|
||||||
|
|
||||||
return <BaseImage src={imageUrl} {...rest} />;
|
return <BaseImage src={imageUrl} unloaderIcon={getUnloaderIcon(props.itemType)} {...rest} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ItemImage = memo(BaseItemImage);
|
export const ItemImage = memo(BaseItemImage);
|
||||||
|
|||||||
@@ -160,7 +160,12 @@ export const AppIcon = {
|
|||||||
edit: LuPencilLine,
|
edit: LuPencilLine,
|
||||||
ellipsisHorizontal: LuEllipsis,
|
ellipsisHorizontal: LuEllipsis,
|
||||||
ellipsisVertical: LuEllipsisVertical,
|
ellipsisVertical: LuEllipsisVertical,
|
||||||
|
emptyAlbumImage: LuDisc3,
|
||||||
|
emptyArtistImage: LuUser,
|
||||||
|
emptyGenreImage: LuFlag,
|
||||||
emptyImage: LuDisc3,
|
emptyImage: LuDisc3,
|
||||||
|
emptyPlaylistImage: LuListMusic,
|
||||||
|
emptySongImage: LuMusic,
|
||||||
error: LuShieldAlert,
|
error: LuShieldAlert,
|
||||||
externalLink: LuExternalLink,
|
externalLink: LuExternalLink,
|
||||||
favorite: LuHeart,
|
favorite: LuHeart,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Img } from 'react-image';
|
|||||||
|
|
||||||
import styles from './image.module.css';
|
import styles from './image.module.css';
|
||||||
|
|
||||||
import { Icon } from '/@/shared/components/icon/icon';
|
import { AppIcon, Icon } from '/@/shared/components/icon/icon';
|
||||||
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||||
import { useInViewport } from '/@/shared/hooks/use-in-viewport';
|
import { useInViewport } from '/@/shared/hooks/use-in-viewport';
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ export interface ImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 's
|
|||||||
includeUnloader?: boolean;
|
includeUnloader?: boolean;
|
||||||
src: string | string[] | undefined;
|
src: string | string[] | undefined;
|
||||||
thumbHash?: string;
|
thumbHash?: string;
|
||||||
|
unloaderIcon?: keyof typeof AppIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImageContainerProps extends HTMLAttributes<HTMLDivElement> {
|
interface ImageContainerProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
@@ -36,6 +37,7 @@ interface ImageLoaderProps {
|
|||||||
|
|
||||||
interface ImageUnloaderProps {
|
interface ImageUnloaderProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
icon?: keyof typeof AppIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FALLBACK_SVG =
|
export const FALLBACK_SVG =
|
||||||
@@ -49,52 +51,40 @@ export function BaseImage({
|
|||||||
includeLoader = true,
|
includeLoader = true,
|
||||||
includeUnloader = true,
|
includeUnloader = true,
|
||||||
src,
|
src,
|
||||||
|
unloaderIcon = 'emptyImage',
|
||||||
...props
|
...props
|
||||||
}: ImageProps) {
|
}: ImageProps) {
|
||||||
const { inViewport, ref } = useInViewport();
|
const { inViewport, ref } = useInViewport();
|
||||||
|
|
||||||
if (src) {
|
|
||||||
return (
|
|
||||||
<Img
|
|
||||||
className={clsx(styles.image, className, {
|
|
||||||
[styles.animated]: enableAnimation,
|
|
||||||
})}
|
|
||||||
container={(children) => (
|
|
||||||
<ImageContainer
|
|
||||||
className={containerClassName}
|
|
||||||
enableAnimation={enableAnimation}
|
|
||||||
ref={ref}
|
|
||||||
{...imageContainerProps}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ImageContainer>
|
|
||||||
)}
|
|
||||||
decoding="async"
|
|
||||||
fetchPriority={inViewport ? 'high' : 'low'}
|
|
||||||
loader={
|
|
||||||
includeLoader ? (
|
|
||||||
<ImageContainer className={containerClassName}>
|
|
||||||
<ImageLoader className={className} />
|
|
||||||
</ImageContainer>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
loading={inViewport ? 'eager' : 'lazy'}
|
|
||||||
src={inViewport ? src : FALLBACK_SVG}
|
|
||||||
unloader={
|
|
||||||
includeUnloader ? (
|
|
||||||
<ImageContainer className={containerClassName}>
|
|
||||||
<ImageUnloader className={className} />
|
|
||||||
</ImageContainer>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageContainer className={containerClassName}>
|
<ImageContainer
|
||||||
<ImageUnloader />
|
className={containerClassName}
|
||||||
|
enableAnimation={enableAnimation}
|
||||||
|
ref={ref}
|
||||||
|
{...imageContainerProps}
|
||||||
|
>
|
||||||
|
{inViewport && src ? (
|
||||||
|
<Img
|
||||||
|
className={clsx(styles.image, className, {
|
||||||
|
[styles.animated]: enableAnimation,
|
||||||
|
})}
|
||||||
|
decoding="async"
|
||||||
|
fetchPriority="high"
|
||||||
|
loader={includeLoader ? <ImageLoader className={className} /> : null}
|
||||||
|
loading="eager"
|
||||||
|
src={src}
|
||||||
|
unloader={
|
||||||
|
includeUnloader ? (
|
||||||
|
<ImageUnloader className={className} icon={unloaderIcon} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
) : !src ? (
|
||||||
|
<ImageUnloader icon={unloaderIcon} />
|
||||||
|
) : (
|
||||||
|
<ImageLoader className={className} />
|
||||||
|
)}
|
||||||
</ImageContainer>
|
</ImageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -131,10 +121,10 @@ export function ImageLoader({ className }: ImageLoaderProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ImageUnloader({ className }: ImageUnloaderProps) {
|
export function ImageUnloader({ className, icon = 'emptyImage' }: ImageUnloaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className={clsx(styles.unloader, className)}>
|
<div className={clsx(styles.unloader, className)}>
|
||||||
<Icon color="default" icon="emptyImage" size="25%" />
|
<Icon color="default" icon={icon} size="25%" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user