improve library header loading

This commit is contained in:
Kendall Garner 2025-06-20 17:57:15 -07:00
parent b8ceb174b3
commit b7fb7c7f94
No known key found for this signature in database
GPG key ID: 9355F387FE765C94
9 changed files with 59 additions and 61 deletions

View file

@ -18,8 +18,9 @@ import { AlbumDetailResponse, LibraryItem, ServerType } from '/@/shared/types/do
interface AlbumDetailHeaderProps { interface AlbumDetailHeaderProps {
background: { background: {
background: string; background?: string;
blur: number; blur: number;
loading: boolean;
}; };
} }

View file

@ -3,7 +3,7 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li
import { useRef } from 'react'; import { useRef } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { NativeScrollArea, Spinner } from '/@/renderer/components'; import { NativeScrollArea } from '/@/renderer/components';
import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content'; import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content';
import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header'; import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header';
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query'; import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
@ -23,7 +23,7 @@ const AlbumDetailRoute = () => {
const { albumId } = useParams() as { albumId: string }; const { albumId } = useParams() as { albumId: string };
const server = useCurrentServer(); const server = useCurrentServer();
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id }); const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
const { color: backgroundColor, colorId } = useFastAverageColor({ const { background: backgroundColor, colorId } = useFastAverageColor({
id: albumId, id: albumId,
src: detailQuery.data?.imageUrl, src: detailQuery.data?.imageUrl,
srcLoaded: !detailQuery.isLoading, srcLoaded: !detailQuery.isLoading,
@ -41,10 +41,6 @@ const AlbumDetailRoute = () => {
}); });
}; };
if (!backgroundColor || colorId !== albumId) {
return <Spinner container />;
}
const backgroundUrl = detailQuery.data?.imageUrl || ''; const backgroundUrl = detailQuery.data?.imageUrl || '';
const background = (albumBackground && `url(${backgroundUrl})`) || backgroundColor; const background = (albumBackground && `url(${backgroundUrl})`) || backgroundColor;
@ -70,6 +66,7 @@ const AlbumDetailRoute = () => {
background={{ background={{
background, background,
blur: (albumBackground && albumBackgroundBlur) || 0, blur: (albumBackground && albumBackgroundBlur) || 0,
loading: !backgroundColor || colorId !== albumId,
}} }}
ref={headerRef} ref={headerRef}
/> />

View file

@ -9,7 +9,7 @@ import { styled } from 'styled-components';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { Button, Spinner, Spoiler, Text } from '/@/renderer/components'; import { Button, Spoiler, Text } from '/@/renderer/components';
import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu'; import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu';
import { SONG_ALBUM_PAGE } from '/@/renderer/features/context-menu/context-menu-items'; import { SONG_ALBUM_PAGE } from '/@/renderer/features/context-menu/context-menu-items';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
@ -55,7 +55,7 @@ const DummyAlbumDetailRoute = () => {
queryKey, queryKey,
}); });
const { color: background, colorId } = useFastAverageColor({ const { background, colorId } = useFastAverageColor({
id: albumId, id: albumId,
src: detailQuery.data?.imageUrl, src: detailQuery.data?.imageUrl,
srcLoaded: !detailQuery.isLoading, srcLoaded: !detailQuery.isLoading,
@ -114,10 +114,6 @@ const DummyAlbumDetailRoute = () => {
}); });
}; };
if (!background || colorId !== albumId) {
return <Spinner container />;
}
const metadataItems = [ const metadataItems = [
{ {
id: 'releaseYear', id: 'releaseYear',
@ -138,6 +134,7 @@ const DummyAlbumDetailRoute = () => {
background={background} background={background}
imageUrl={detailQuery?.data?.imageUrl} imageUrl={detailQuery?.data?.imageUrl}
item={{ route: AppRoute.LIBRARY_SONGS, type: LibraryItem.SONG }} item={{ route: AppRoute.LIBRARY_SONGS, type: LibraryItem.SONG }}
loading={!background || colorId !== albumId}
title={detailQuery?.data?.name || ''} title={detailQuery?.data?.name || ''}
> >
<Stack spacing="sm"> <Stack spacing="sm">

View file

@ -12,11 +12,12 @@ import { formatDurationString } from '/@/renderer/utils';
import { LibraryItem, ServerType } from '/@/shared/types/domain-types'; import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
interface AlbumArtistDetailHeaderProps { interface AlbumArtistDetailHeaderProps {
background: string; background?: string;
loading: boolean;
} }
export const AlbumArtistDetailHeader = forwardRef( export const AlbumArtistDetailHeader = forwardRef(
({ background }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => { ({ background, loading }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
const { albumArtistId, artistId } = useParams() as { const { albumArtistId, artistId } = useParams() as {
albumArtistId?: string; albumArtistId?: string;
artistId?: string; artistId?: string;
@ -76,6 +77,7 @@ export const AlbumArtistDetailHeader = forwardRef(
background={background} background={background}
imageUrl={detailQuery?.data?.imageUrl} imageUrl={detailQuery?.data?.imageUrl}
item={{ route: AppRoute.LIBRARY_ALBUM_ARTISTS, type: LibraryItem.ALBUM_ARTIST }} item={{ route: AppRoute.LIBRARY_ALBUM_ARTISTS, type: LibraryItem.ALBUM_ARTIST }}
loading={loading}
ref={ref} ref={ref}
title={detailQuery?.data?.name || ''} title={detailQuery?.data?.name || ''}
> >

View file

@ -1,7 +1,7 @@
import { useRef } from 'react'; import { useRef } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { NativeScrollArea, Spinner } from '/@/renderer/components'; import { NativeScrollArea } from '/@/renderer/components';
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content'; import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
import { AlbumArtistDetailHeader } from '/@/renderer/features/artists/components/album-artist-detail-header'; import { AlbumArtistDetailHeader } from '/@/renderer/features/artists/components/album-artist-detail-header';
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query'; import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
@ -30,7 +30,7 @@ const AlbumArtistDetailRoute = () => {
query: { id: routeId }, query: { id: routeId },
serverId: server?.id, serverId: server?.id,
}); });
const { color: background, colorId } = useFastAverageColor({ const { background, colorId } = useFastAverageColor({
id: routeId, id: routeId,
src: detailQuery.data?.imageUrl, src: detailQuery.data?.imageUrl,
srcLoaded: !detailQuery.isLoading, srcLoaded: !detailQuery.isLoading,
@ -46,10 +46,6 @@ const AlbumArtistDetailRoute = () => {
}); });
}; };
if (!background || colorId !== routeId) {
return <Spinner container />;
}
return ( return (
<AnimatedPage key={`album-artist-detail-${routeId}`}> <AnimatedPage key={`album-artist-detail-${routeId}`}>
<NativeScrollArea <NativeScrollArea
@ -70,6 +66,7 @@ const AlbumArtistDetailRoute = () => {
> >
<AlbumArtistDetailHeader <AlbumArtistDetailHeader
background={background} background={background}
loading={!background || colorId !== routeId}
ref={headerRef} ref={headerRef}
/> />
<AlbumArtistDetailContent background={background} /> <AlbumArtistDetailContent background={background} />

View file

@ -132,7 +132,7 @@ export const FullScreenPlayerImage = () => {
const { queue } = usePlayerData(); const { queue } = usePlayerData();
const { useImageAspectRatio } = useFullScreenPlayerStore(); const { useImageAspectRatio } = useFullScreenPlayerStore();
const currentSong = queue.current; const currentSong = queue.current;
const { color: background } = useFastAverageColor({ const { background } = useFastAverageColor({
algorithm: 'dominant', algorithm: 'dominant',
src: queue.current?.imageUrl, src: queue.current?.imageUrl,
srcLoaded: true, srcLoaded: true,

View file

@ -472,7 +472,7 @@ export const FullScreenPlayer = () => {
}, [location, setStore]); }, [location, setStore]);
const currentSong = useCurrentSong(); const currentSong = useCurrentSong();
const { color: background } = useFastAverageColor({ const { background } = useFastAverageColor({
algorithm: 'dominant', algorithm: 'dominant',
src: currentSong?.imageUrl, src: currentSong?.imageUrl,
srcLoaded: true, srcLoaded: true,

View file

@ -14,18 +14,19 @@ import { useGeneralSettings } from '/@/renderer/store';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain-types';
interface LibraryHeaderProps { interface LibraryHeaderProps {
background: string; background?: string;
blur?: number; blur?: number;
children?: ReactNode; children?: ReactNode;
imagePlaceholderUrl?: null | string; imagePlaceholderUrl?: null | string;
imageUrl?: null | string; imageUrl?: null | string;
item: { route: string; type: LibraryItem }; item: { route: string; type: LibraryItem };
loading?: boolean;
title: string; title: string;
} }
export const LibraryHeader = forwardRef( export const LibraryHeader = forwardRef(
( (
{ background, blur, children, imageUrl, item, title }: LibraryHeaderProps, { background, blur, children, imageUrl, item, loading, title }: LibraryHeaderProps,
ref: Ref<HTMLDivElement>, ref: Ref<HTMLDivElement>,
) => { ) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -106,38 +107,41 @@ export const LibraryHeader = forwardRef(
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
tabIndex={0} tabIndex={0}
> >
{imageUrl && !isImageError ? ( {!loading &&
<img (imageUrl && !isImageError ? (
alt="cover" <img
className={styles.image} alt="cover"
onError={onImageError} className={styles.image}
// placeholder={imagePlaceholderUrl || 'var(--placeholder-bg)'} onError={onImageError}
src={imageUrl} // placeholder={imagePlaceholderUrl || 'var(--placeholder-bg)'}
style={{ height: '' }} src={imageUrl}
/> style={{ height: '' }}
) : ( />
<ItemImagePlaceholder itemType={item.type} /> ) : (
)} <ItemImagePlaceholder itemType={item.type} />
</div> ))}
<div className={styles.metadataSection}>
<Group>
<h2>
<Text
$link
component={Link}
to={item.route}
tt="uppercase"
weight={600}
>
{itemTypeString()}
</Text>
</h2>
</Group>
<h1 className={styles.title}>
<AutoTextSize mode="box">{title}</AutoTextSize>
</h1>
{children}
</div> </div>
{title && (
<div className={styles.metadataSection}>
<Group>
<h2>
<Text
$link
component={Link}
to={item.route}
tt="uppercase"
weight={600}
>
{itemTypeString()}
</Text>
</h2>
</Group>
<h1 className={styles.title}>
<AutoTextSize mode="box">{title}</AutoTextSize>
</h1>
{children}
</div>
)}
</div> </div>
); );
}, },

View file

@ -10,7 +10,7 @@ export const useFastAverageColor = (args: {
const { algorithm, id, src, srcLoaded } = args; const { algorithm, id, src, srcLoaded } = args;
const idRef = useRef<string | undefined>(id); const idRef = useRef<string | undefined>(id);
const [color, setColor] = useState<string | undefined>(undefined); const [background, setBackground] = useState<string | undefined>(undefined);
useEffect(() => { useEffect(() => {
const fac = new FastAverageColor(); const fac = new FastAverageColor();
@ -27,16 +27,16 @@ export const useFastAverageColor = (args: {
}) })
.then((color) => { .then((color) => {
idRef.current = id; idRef.current = id;
return setColor(color.rgb); return setBackground(color.rgb);
}) })
.catch((e) => { .catch((e) => {
console.log('Error fetching average color', e); console.log('Error fetching average color', e);
idRef.current = id; idRef.current = id;
return setColor('rgba(0, 0, 0, 0)'); return setBackground('rgba(0, 0, 0, 0)');
}); });
} else if (srcLoaded) { } else if (srcLoaded) {
idRef.current = id; idRef.current = id;
return setColor('var(--placeholder-bg)'); return setBackground('var(--placeholder-bg)');
} }
return () => { return () => {
@ -44,5 +44,5 @@ export const useFastAverageColor = (args: {
}; };
}, [algorithm, srcLoaded, src, id]); }, [algorithm, srcLoaded, src, id]);
return { color, colorId: idRef.current }; return { background, colorId: idRef.current };
}; };