mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-08 21:10:12 +02:00
Add image URL generation at runtime to allow for dynamic image sizes (#1439)
* add getImageUrl to domain endpoints * add new ItemImage component and hooks to generate image url * add configuration for image resolution based on types
This commit is contained in:
@@ -6,6 +6,7 @@ import { generatePath, Link } from 'react-router';
|
||||
|
||||
import styles from './feature-carousel.module.css';
|
||||
|
||||
import { ItemImage, useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { BackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
|
||||
import { PlayButtonGroup } from '/@/renderer/features/shared/components/play-button-group';
|
||||
@@ -15,7 +16,6 @@ import { useCurrentServer } from '/@/renderer/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Badge } from '/@/shared/components/badge/badge';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Album, LibraryItem } from '/@/shared/types/domain-types';
|
||||
@@ -78,9 +78,15 @@ interface CarouselItemProps {
|
||||
}
|
||||
|
||||
const CarouselItem = ({ album }: CarouselItemProps) => {
|
||||
const imageUrl = useItemImageUrl({
|
||||
id: album.id,
|
||||
itemType: LibraryItem.ALBUM,
|
||||
type: 'itemCard',
|
||||
});
|
||||
|
||||
const { background: backgroundColor } = useFastAverageColor({
|
||||
algorithm: 'dominant',
|
||||
src: album.imageUrl || null,
|
||||
src: imageUrl || null,
|
||||
srcLoaded: true,
|
||||
});
|
||||
|
||||
@@ -110,10 +116,12 @@ const CarouselItem = ({ album }: CarouselItemProps) => {
|
||||
</div>
|
||||
|
||||
<div className={styles.imageSection}>
|
||||
<Image
|
||||
<ItemImage
|
||||
className={styles.albumImage}
|
||||
containerClassName={styles.albumImageContainer}
|
||||
src={album.imageUrl || undefined}
|
||||
id={album.id}
|
||||
itemType={LibraryItem.ALBUM}
|
||||
src={imageUrl}
|
||||
/>
|
||||
<div className={styles.playButtonOverlay}>
|
||||
<PlayButtonGroup onPlay={handlePlay} />
|
||||
|
||||
@@ -7,6 +7,7 @@ import { generatePath, Link } from 'react-router';
|
||||
import styles from './item-card.module.css';
|
||||
|
||||
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
||||
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
||||
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
||||
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
|
||||
import {
|
||||
@@ -18,7 +19,6 @@ import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { formatDateAbsolute, formatDateRelative, formatRating } from '/@/renderer/utils/format';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Separator } from '/@/shared/components/separator/separator';
|
||||
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
@@ -137,7 +137,6 @@ const CompactItemCard = ({
|
||||
data,
|
||||
enableExpansion,
|
||||
enableNavigation,
|
||||
imageUrl,
|
||||
internalState,
|
||||
isRound,
|
||||
itemType,
|
||||
@@ -247,11 +246,13 @@ const CompactItemCard = ({
|
||||
|
||||
const imageContainerContent = (
|
||||
<>
|
||||
<Image
|
||||
<ItemImage
|
||||
className={clsx(styles.image, {
|
||||
[styles.isRound]: isRound,
|
||||
})}
|
||||
src={imageUrl}
|
||||
id={data?.id}
|
||||
itemType={itemType}
|
||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||
/>
|
||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
@@ -351,7 +352,6 @@ const DefaultItemCard = ({
|
||||
data,
|
||||
enableExpansion,
|
||||
enableNavigation,
|
||||
imageUrl,
|
||||
internalState,
|
||||
isRound,
|
||||
itemType,
|
||||
@@ -461,9 +461,11 @@ const DefaultItemCard = ({
|
||||
|
||||
const imageContainerContent = (
|
||||
<>
|
||||
<Image
|
||||
<ItemImage
|
||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||
src={imageUrl}
|
||||
id={data?.id}
|
||||
itemType={itemType}
|
||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||
/>
|
||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
@@ -563,7 +565,6 @@ const PosterItemCard = ({
|
||||
enableDrag,
|
||||
enableExpansion,
|
||||
enableNavigation,
|
||||
imageUrl,
|
||||
internalState,
|
||||
isRound,
|
||||
itemType,
|
||||
@@ -720,9 +721,11 @@ const PosterItemCard = ({
|
||||
|
||||
const imageContainerContent = (
|
||||
<>
|
||||
<Image
|
||||
<ItemImage
|
||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||
src={imageUrl}
|
||||
id={data?.id}
|
||||
itemType={itemType}
|
||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||
/>
|
||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
|
||||
@@ -1,146 +1,146 @@
|
||||
import { AnimatePresence } from 'motion/react';
|
||||
import { MouseEvent, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
// import { AnimatePresence } from 'motion/react';
|
||||
// import { MouseEvent, useMemo, useState } from 'react';
|
||||
// import { Link } from 'react-router';
|
||||
|
||||
import styles from './item-detail.module.css';
|
||||
// import styles from './item-detail.module.css';
|
||||
|
||||
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Badge } from '/@/shared/components/badge/badge';
|
||||
import { Divider } from '/@/shared/components/divider/divider';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
Artist,
|
||||
LibraryItem,
|
||||
Playlist,
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { stringToColor } from '/@/shared/utils/string-to-color';
|
||||
// import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
||||
// import { useFastAverageColor } from '/@/renderer/hooks';
|
||||
// import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
// import { Badge } from '/@/shared/components/badge/badge';
|
||||
// import { Divider } from '/@/shared/components/divider/divider';
|
||||
// import { Group } from '/@/shared/components/group/group';
|
||||
// import { Image } from '/@/shared/components/image/image';
|
||||
// import { Rating } from '/@/shared/components/rating/rating';
|
||||
// import { Text } from '/@/shared/components/text/text';
|
||||
// import {
|
||||
// Album,
|
||||
// AlbumArtist,
|
||||
// Artist,
|
||||
// LibraryItem,
|
||||
// Playlist,
|
||||
// Song,
|
||||
// } from '/@/shared/types/domain-types';
|
||||
// import { stringToColor } from '/@/shared/utils/string-to-color';
|
||||
|
||||
interface ItemDetailProps {
|
||||
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
||||
itemHeight: number;
|
||||
itemType: LibraryItem;
|
||||
onClick?: (e: MouseEvent<HTMLDivElement>, item: unknown, itemType: LibraryItem) => void;
|
||||
withControls?: boolean;
|
||||
}
|
||||
// interface ItemDetailProps {
|
||||
// data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
||||
// itemHeight: number;
|
||||
// itemType: LibraryItem;
|
||||
// onClick?: (e: MouseEvent<HTMLDivElement>, item: unknown, itemType: LibraryItem) => void;
|
||||
// withControls?: boolean;
|
||||
// }
|
||||
|
||||
export const ItemDetail = ({ data, itemType, onClick, withControls }: ItemDetailProps) => {
|
||||
const imageUrl = getImageUrl(data);
|
||||
// export const ItemDetail = ({ data, itemType, onClick, withControls }: ItemDetailProps) => {
|
||||
// const imageUrl = getImageUrl(data);
|
||||
|
||||
const [showControls, setShowControls] = useState(false);
|
||||
// const [showControls, setShowControls] = useState(false);
|
||||
|
||||
const { background } = useFastAverageColor({
|
||||
algorithm: 'simple',
|
||||
src: imageUrl,
|
||||
srcLoaded: false,
|
||||
});
|
||||
// const { background } = useFastAverageColor({
|
||||
// algorithm: 'simple',
|
||||
// src: imageUrl,
|
||||
// srcLoaded: false,
|
||||
// });
|
||||
|
||||
// const tags = [...(data?.genres ?? [])];
|
||||
// // const tags = [...(data?.genres ?? [])];
|
||||
|
||||
const tags = useMemo(() => {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
// const tags = useMemo(() => {
|
||||
// if (!data) {
|
||||
// return [];
|
||||
// }
|
||||
|
||||
const items: {
|
||||
color?: string;
|
||||
id: string;
|
||||
isLight?: boolean;
|
||||
itemType: LibraryItem;
|
||||
name: string;
|
||||
}[] = [];
|
||||
// const items: {
|
||||
// color?: string;
|
||||
// id: string;
|
||||
// isLight?: boolean;
|
||||
// itemType: LibraryItem;
|
||||
// name: string;
|
||||
// }[] = [];
|
||||
|
||||
if ('albumArtists' in data && Array.isArray(data.albumArtists)) {
|
||||
data.albumArtists?.forEach((tag: { id: string; name: string }) => {
|
||||
items.push({ id: tag.id, itemType: LibraryItem.ALBUM_ARTIST, name: tag.name });
|
||||
});
|
||||
}
|
||||
// if ('albumArtists' in data && Array.isArray(data.albumArtists)) {
|
||||
// data.albumArtists?.forEach((tag: { id: string; name: string }) => {
|
||||
// items.push({ id: tag.id, itemType: LibraryItem.ALBUM_ARTIST, name: tag.name });
|
||||
// });
|
||||
// }
|
||||
|
||||
if ('genres' in data && Array.isArray(data.genres)) {
|
||||
data.genres?.forEach((tag: { id: string; itemType: LibraryItem; name: string }) => {
|
||||
const { color, isLight } = stringToColor(tag.name);
|
||||
items.push({ ...tag, color, isLight });
|
||||
});
|
||||
}
|
||||
// if ('genres' in data && Array.isArray(data.genres)) {
|
||||
// data.genres?.forEach((tag: { id: string; itemType: LibraryItem; name: string }) => {
|
||||
// const { color, isLight } = stringToColor(tag.name);
|
||||
// items.push({ ...tag, color, isLight });
|
||||
// });
|
||||
// }
|
||||
|
||||
// if ('tags' in data && typeof data.tags === 'object') {
|
||||
// console.log('data.tags :>> ', data.tags);
|
||||
// Object.entries(data.tags).forEach(([key, value]) => {
|
||||
// items.push({ id: key, itemType: LibraryItem.TAG, name: value });
|
||||
// });
|
||||
// }
|
||||
// // if ('tags' in data && typeof data.tags === 'object') {
|
||||
// // console.log('data.tags :>> ', data.tags);
|
||||
// // Object.entries(data.tags).forEach(([key, value]) => {
|
||||
// // items.push({ id: key, itemType: LibraryItem.TAG, name: value });
|
||||
// // });
|
||||
// // }
|
||||
|
||||
return items;
|
||||
}, [data]);
|
||||
// return items;
|
||||
// }, [data]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.container}
|
||||
onClick={(e) => onClick?.(e, data, itemType)}
|
||||
style={{ backgroundColor: background }}
|
||||
>
|
||||
<div
|
||||
className={styles.imageContainer}
|
||||
onMouseEnter={() => withControls && setShowControls(true)}
|
||||
onMouseLeave={() => withControls && setShowControls(false)}
|
||||
>
|
||||
<Image alt={data?.name} src={imageUrl} />
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && <ItemCardControls type="compact" />}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<div className={styles.metadataContainer}>
|
||||
<div className={styles.header}>
|
||||
<Text className={styles.title} component={Link} isLink size="lg" weight={500}>
|
||||
{data?.name}
|
||||
</Text>
|
||||
<Group>
|
||||
{data && 'userRating' in data && (
|
||||
<Rating size="xs" value={data?.userRating ?? 0} />
|
||||
)}
|
||||
{data && 'userFavorite' in data && (
|
||||
<ActionIcon
|
||||
icon="favorite"
|
||||
iconProps={{
|
||||
fill: data?.userFavorite ? 'primary' : 'default',
|
||||
}}
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className={styles.content}>
|
||||
<Group className={styles.tags} gap="xs">
|
||||
{tags.map((tag) => (
|
||||
<Badge
|
||||
key={tag.id}
|
||||
style={{
|
||||
backgroundColor: tag.color,
|
||||
color: tag.isLight ? 'black' : 'white',
|
||||
}}
|
||||
>
|
||||
{tag.name}
|
||||
</Badge>
|
||||
))}
|
||||
</Group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// return (
|
||||
// <div
|
||||
// className={styles.container}
|
||||
// onClick={(e) => onClick?.(e, data, itemType)}
|
||||
// style={{ backgroundColor: background }}
|
||||
// >
|
||||
// <div
|
||||
// className={styles.imageContainer}
|
||||
// onMouseEnter={() => withControls && setShowControls(true)}
|
||||
// onMouseLeave={() => withControls && setShowControls(false)}
|
||||
// >
|
||||
// <Image alt={data?.name} src={imageUrl} />
|
||||
// <AnimatePresence>
|
||||
// {withControls && showControls && <ItemCardControls type="compact" />}
|
||||
// </AnimatePresence>
|
||||
// </div>
|
||||
// <div className={styles.metadataContainer}>
|
||||
// <div className={styles.header}>
|
||||
// <Text className={styles.title} component={Link} isLink size="lg" weight={500}>
|
||||
// {data?.name}
|
||||
// </Text>
|
||||
// <Group>
|
||||
// {data && 'userRating' in data && (
|
||||
// <Rating size="xs" value={data?.userRating ?? 0} />
|
||||
// )}
|
||||
// {data && 'userFavorite' in data && (
|
||||
// <ActionIcon
|
||||
// icon="favorite"
|
||||
// iconProps={{
|
||||
// fill: data?.userFavorite ? 'primary' : 'default',
|
||||
// }}
|
||||
// size="xs"
|
||||
// />
|
||||
// )}
|
||||
// </Group>
|
||||
// </div>
|
||||
// <Divider />
|
||||
// <div className={styles.content}>
|
||||
// <Group className={styles.tags} gap="xs">
|
||||
// {tags.map((tag) => (
|
||||
// <Badge
|
||||
// key={tag.id}
|
||||
// style={{
|
||||
// backgroundColor: tag.color,
|
||||
// color: tag.isLight ? 'black' : 'white',
|
||||
// }}
|
||||
// >
|
||||
// {tag.name}
|
||||
// </Badge>
|
||||
// ))}
|
||||
// </Group>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | undefined) => {
|
||||
if (data && 'imageUrl' in data) {
|
||||
return data.imageUrl || undefined;
|
||||
}
|
||||
// const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | undefined) => {
|
||||
// if (data && 'imageUrl' in data) {
|
||||
// return data.imageUrl || undefined;
|
||||
// }
|
||||
|
||||
return undefined;
|
||||
};
|
||||
// return undefined;
|
||||
// };
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import z from 'zod';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { GeneralSettingsSchema, useCurrentServerId, useSettingsStore } from '/@/renderer/store';
|
||||
import { BaseImage, ImageProps } from '/@/shared/components/image/image';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
const BaseItemImage = (
|
||||
props: Omit<ImageProps, 'src'> & {
|
||||
id?: null | string;
|
||||
itemType: LibraryItem;
|
||||
src?: null | string;
|
||||
},
|
||||
) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { src, ...rest } = props;
|
||||
|
||||
const imageUrl = useItemImageUrl({ id: props.id, itemType: props.itemType, size: 300 });
|
||||
|
||||
return <BaseImage src={imageUrl} {...rest} />;
|
||||
};
|
||||
|
||||
export const ItemImage = memo(BaseItemImage);
|
||||
|
||||
interface UseItemImageUrlProps {
|
||||
id?: string;
|
||||
imageUrl?: null | string;
|
||||
itemType: LibraryItem;
|
||||
size?: number;
|
||||
type?: keyof z.infer<typeof GeneralSettingsSchema>['imageRes'];
|
||||
}
|
||||
|
||||
export const useItemImageUrl = (args: UseItemImageUrlProps) => {
|
||||
const { id, imageUrl, itemType, size, type } = args;
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const imageRes = useSettingsStore((store) => store.general.imageRes);
|
||||
const sizeByType: number | undefined = type ? imageRes[type] : undefined;
|
||||
|
||||
return useMemo(() => {
|
||||
if (imageUrl) {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
api.controller.getImageUrl({
|
||||
apiClientProps: { serverId },
|
||||
query: { id, itemType, size: size ?? sizeByType },
|
||||
}) || undefined
|
||||
);
|
||||
}, [id, imageUrl, itemType, serverId, size, sizeByType]);
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import { useState } from 'react';
|
||||
|
||||
import styles from './image-column.module.css';
|
||||
|
||||
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
||||
import {
|
||||
ItemTableListInnerColumn,
|
||||
TableColumnContainer,
|
||||
@@ -14,17 +15,14 @@ import {
|
||||
} from '/@/renderer/features/shared/components/play-button-group';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||
import { Folder, LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
export const ImageColumn = (props: ItemTableListInnerColumn) => {
|
||||
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[
|
||||
props.columns[props.columnIndex].id
|
||||
];
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.id;
|
||||
const item = props.data[props.rowIndex] as any;
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
const internalState = (props as any).internalState;
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
@@ -80,12 +78,14 @@ export const ImageColumn = (props: ItemTableListInnerColumn) => {
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<Image
|
||||
<ItemImage
|
||||
containerClassName={clsx({
|
||||
[styles.imageContainerWithAspectRatio]:
|
||||
props.size === 'default' || props.size === 'large',
|
||||
})}
|
||||
src={row}
|
||||
id={item?.id}
|
||||
itemType={item?._itemType}
|
||||
src={item?.imageUrl}
|
||||
/>
|
||||
{isHovered && (
|
||||
<div
|
||||
|
||||
+14
-4
@@ -4,6 +4,7 @@ import { generatePath, Link } from 'react-router';
|
||||
|
||||
import styles from './title-combined-column.module.css';
|
||||
|
||||
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
||||
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
|
||||
import {
|
||||
ColumnNullFallback,
|
||||
@@ -19,13 +20,12 @@ import {
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Folder, LibraryItem, QueueSong, RelatedAlbumArtist } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
|
||||
const row: object | undefined = (props.data as (any | undefined)[])[props.rowIndex];
|
||||
const row: object | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.id;
|
||||
const item = props.data[props.rowIndex] as any;
|
||||
const internalState = (props as any).internalState;
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
@@ -110,7 +110,12 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<Image containerClassName={styles.image} src={row.imageUrl as string} />
|
||||
<ItemImage
|
||||
containerClassName={styles.image}
|
||||
id={item?.id}
|
||||
itemType={item?._itemType}
|
||||
src={item?.imageUrl}
|
||||
/>
|
||||
{isHovered && (
|
||||
<div
|
||||
className={clsx(styles.playButtonOverlay, {
|
||||
@@ -263,7 +268,12 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<Image containerClassName={styles.image} src={row.imageUrl as string} />
|
||||
<ItemImage
|
||||
containerClassName={styles.image}
|
||||
id={item?.id}
|
||||
itemType={item?._itemType}
|
||||
src={item?.imageUrl}
|
||||
/>
|
||||
{isHovered && (
|
||||
<div
|
||||
className={clsx(styles.playButtonOverlay, {
|
||||
|
||||
Reference in New Issue
Block a user