mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
add LibraryContainer for max-width and background overlay
This commit is contained in:
@@ -12,7 +12,6 @@ import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/
|
|||||||
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||||
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel';
|
import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel';
|
||||||
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
|
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { searchLibraryItems } from '/@/renderer/features/shared/utils';
|
import { searchLibraryItems } from '/@/renderer/features/shared/utils';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
@@ -49,9 +48,6 @@ import {
|
|||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
import { ItemListKey, ListDisplayType } from '/@/shared/types/types';
|
import { ItemListKey, ListDisplayType } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface AlbumDetailContentProps {
|
|
||||||
background?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AlbumMetadataTagsProps {
|
interface AlbumMetadataTagsProps {
|
||||||
album: Album | undefined;
|
album: Album | undefined;
|
||||||
@@ -316,7 +312,7 @@ const AlbumMetadataExternalLinks = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AlbumDetailContent = ({ background }: AlbumDetailContentProps) => {
|
export const AlbumDetailContent = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { albumId } = useParams() as { albumId: string };
|
const { albumId } = useParams() as { albumId: string };
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
@@ -370,7 +366,6 @@ export const AlbumDetailContent = ({ background }: AlbumDetailContentProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.contentContainer} ref={ref}>
|
<div className={styles.contentContainer} ref={ref}>
|
||||||
<LibraryBackgroundOverlay backgroundColor={background} />
|
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{comment && <Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>}
|
{comment && <Spoiler maxHeight={75}>{replaceURLWithHTMLLinks(comment)}</Spoiler>}
|
||||||
<div className={styles.contentLayout}>
|
<div className={styles.contentLayout}>
|
||||||
|
|||||||
@@ -16,93 +16,82 @@ import { Stack } from '/@/shared/components/stack/stack';
|
|||||||
import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
|
import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface AlbumDetailHeaderProps {
|
export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
|
||||||
background: {
|
const { albumId } = useParams() as { albumId: string };
|
||||||
background?: string;
|
const server = useCurrentServer();
|
||||||
blur: number;
|
const detailQuery = useQuery(
|
||||||
loading: boolean;
|
albumQueries.detail({ query: { id: albumId }, serverId: server?.id }),
|
||||||
};
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export const AlbumDetailHeader = forwardRef<HTMLDivElement, AlbumDetailHeaderProps>(
|
const showRating =
|
||||||
({ background }, ref) => {
|
detailQuery?.data?._serverType === ServerType.NAVIDROME ||
|
||||||
const { albumId } = useParams() as { albumId: string };
|
detailQuery?.data?._serverType === ServerType.SUBSONIC;
|
||||||
const server = useCurrentServer();
|
|
||||||
const detailQuery = useQuery(
|
const { addToQueueByFetch, setFavorite, setRating } = usePlayer();
|
||||||
albumQueries.detail({ query: { id: albumId }, serverId: server?.id }),
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
|
|
||||||
|
const handleFavorite = () => {
|
||||||
|
if (!detailQuery?.data) return;
|
||||||
|
setFavorite(
|
||||||
|
detailQuery.data._serverId,
|
||||||
|
[detailQuery.data.id],
|
||||||
|
LibraryItem.ALBUM,
|
||||||
|
!detailQuery.data.userFavorite,
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const showRating =
|
const handleUpdateRating = showRating
|
||||||
detailQuery?.data?._serverType === ServerType.NAVIDROME ||
|
? (rating: number) => {
|
||||||
detailQuery?.data?._serverType === ServerType.SUBSONIC;
|
if (!detailQuery?.data) return;
|
||||||
|
|
||||||
const { addToQueueByFetch, setFavorite, setRating } = usePlayer();
|
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
|
||||||
|
|
||||||
const handleFavorite = () => {
|
|
||||||
if (!detailQuery?.data) return;
|
|
||||||
setFavorite(
|
|
||||||
detailQuery.data._serverId,
|
|
||||||
[detailQuery.data.id],
|
|
||||||
LibraryItem.ALBUM,
|
|
||||||
!detailQuery.data.userFavorite,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUpdateRating = showRating
|
|
||||||
? (rating: number) => {
|
|
||||||
if (!detailQuery?.data) return;
|
|
||||||
|
|
||||||
if (detailQuery.data.userRating === rating) {
|
|
||||||
return setRating(
|
|
||||||
detailQuery.data._serverId,
|
|
||||||
[detailQuery.data.id],
|
|
||||||
LibraryItem.ALBUM,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (detailQuery.data.userRating === rating) {
|
||||||
return setRating(
|
return setRating(
|
||||||
detailQuery.data._serverId,
|
detailQuery.data._serverId,
|
||||||
[detailQuery.data.id],
|
[detailQuery.data.id],
|
||||||
LibraryItem.ALBUM,
|
LibraryItem.ALBUM,
|
||||||
rating,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const handlePlay = (type?: Play) => {
|
return setRating(
|
||||||
if (!server?.id || !albumId) return;
|
detailQuery.data._serverId,
|
||||||
addToQueueByFetch(server.id, [albumId], LibraryItem.ALBUM, type || playButtonBehavior);
|
[detailQuery.data.id],
|
||||||
};
|
LibraryItem.ALBUM,
|
||||||
|
rating,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const handleMoreOptions = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handlePlay = (type?: Play) => {
|
||||||
if (!detailQuery?.data) return;
|
if (!server?.id || !albumId) return;
|
||||||
ContextMenuController.call({
|
addToQueueByFetch(server.id, [albumId], LibraryItem.ALBUM, type || playButtonBehavior);
|
||||||
cmd: { items: [detailQuery.data], type: LibraryItem.ALBUM },
|
};
|
||||||
event: e,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
const handleMoreOptions = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
<Stack ref={ref}>
|
if (!detailQuery?.data) return;
|
||||||
<LibraryHeader
|
ContextMenuController.call({
|
||||||
imageUrl={detailQuery?.data?.imageUrl}
|
cmd: { items: [detailQuery.data], type: LibraryItem.ALBUM },
|
||||||
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
event: e,
|
||||||
title={detailQuery?.data?.name || ''}
|
});
|
||||||
{...background}
|
};
|
||||||
>
|
|
||||||
<LibraryHeaderMenu
|
return (
|
||||||
favorite={detailQuery?.data?.userFavorite}
|
<Stack ref={ref}>
|
||||||
onFavorite={handleFavorite}
|
<LibraryHeader
|
||||||
onMore={handleMoreOptions}
|
imageUrl={detailQuery?.data?.imageUrl}
|
||||||
onPlay={() => handlePlay(Play.NOW)}
|
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
||||||
onRating={handleUpdateRating}
|
title={detailQuery?.data?.name || ''}
|
||||||
onShuffle={() => handlePlay(Play.SHUFFLE)}
|
>
|
||||||
rating={detailQuery?.data?.userRating || 0}
|
<LibraryHeaderMenu
|
||||||
/>
|
favorite={detailQuery?.data?.userFavorite}
|
||||||
</LibraryHeader>
|
onFavorite={handleFavorite}
|
||||||
</Stack>
|
onMore={handleMoreOptions}
|
||||||
);
|
onPlay={() => handlePlay(Play.NOW)}
|
||||||
},
|
onRating={handleUpdateRating}
|
||||||
);
|
onShuffle={() => handlePlay(Play.SHUFFLE)}
|
||||||
|
rating={detailQuery?.data?.userRating || 0}
|
||||||
|
/>
|
||||||
|
</LibraryHeader>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
|||||||
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 { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
|
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
|
||||||
|
import {
|
||||||
|
LibraryBackgroundImage,
|
||||||
|
LibraryBackgroundOverlay,
|
||||||
|
} from '/@/renderer/features/shared/components/library-background-overlay';
|
||||||
|
import { LibraryContainer } from '/@/renderer/features/shared/components/library-container';
|
||||||
import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar';
|
import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar';
|
||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
import { useCurrentServer, useGeneralSettings } from '/@/renderer/store';
|
import { useCurrentServer, useGeneralSettings } from '/@/renderer/store';
|
||||||
@@ -28,14 +33,15 @@ const AlbumDetailRoute = () => {
|
|||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { background: backgroundColor, colorId } = useFastAverageColor({
|
const { background: backgroundColor } = useFastAverageColor({
|
||||||
id: albumId,
|
id: albumId,
|
||||||
src: detailQuery.data?.imageUrl,
|
src: detailQuery.data?.imageUrl,
|
||||||
srcLoaded: !detailQuery.isLoading,
|
srcLoaded: !detailQuery.isLoading,
|
||||||
});
|
});
|
||||||
|
|
||||||
const backgroundUrl = detailQuery.data?.imageUrl || '';
|
const background = backgroundColor;
|
||||||
const background = (albumBackground && `url(${backgroundUrl})`) || backgroundColor;
|
|
||||||
|
const showBlurredImage = Boolean(detailQuery.data?.imageUrl) && albumBackground;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPage key={`album-detail-${albumId}`}>
|
<AnimatedPage key={`album-detail-${albumId}`}>
|
||||||
@@ -59,15 +65,19 @@ const AlbumDetailRoute = () => {
|
|||||||
}}
|
}}
|
||||||
ref={scrollAreaRef}
|
ref={scrollAreaRef}
|
||||||
>
|
>
|
||||||
<AlbumDetailHeader
|
{showBlurredImage ? (
|
||||||
background={{
|
<LibraryBackgroundImage
|
||||||
background,
|
blur={albumBackgroundBlur}
|
||||||
blur: (albumBackground && albumBackgroundBlur) || 0,
|
headerRef={headerRef}
|
||||||
loading: !backgroundColor || colorId !== albumId,
|
imageUrl={detailQuery.data.imageUrl!}
|
||||||
}}
|
/>
|
||||||
ref={headerRef}
|
) : (
|
||||||
/>
|
<LibraryBackgroundOverlay backgroundColor={background} headerRef={headerRef} />
|
||||||
<AlbumDetailContent background={background} />
|
)}
|
||||||
|
<LibraryContainer>
|
||||||
|
<AlbumDetailHeader ref={headerRef as React.Ref<HTMLDivElement>} />
|
||||||
|
<AlbumDetailContent />
|
||||||
|
</LibraryContainer>
|
||||||
</NativeScrollArea>
|
</NativeScrollArea>
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
|||||||
import { AlbumArtistGridCarousel } from '/@/renderer/features/artists/components/album-artist-grid-carousel';
|
import { AlbumArtistGridCarousel } from '/@/renderer/features/artists/components/album-artist-grid-carousel';
|
||||||
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
|
|
||||||
import { PlayButton } from '/@/renderer/features/shared/components/play-button';
|
import { PlayButton } from '/@/renderer/features/shared/components/play-button';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
|
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
|
||||||
@@ -35,11 +34,7 @@ import {
|
|||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface AlbumArtistDetailContentProps {
|
export const AlbumArtistDetailContent = () => {
|
||||||
background?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailContentProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { artistItems, externalLinks, lastFM, musicBrainz } = useGeneralSettings();
|
const { artistItems, externalLinks, lastFM, musicBrainz } = useGeneralSettings();
|
||||||
const { albumArtistId, artistId } = useParams() as {
|
const { albumArtistId, artistId } = useParams() as {
|
||||||
@@ -230,7 +225,6 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.contentContainer} ref={ref}>
|
<div className={styles.contentContainer} ref={ref}>
|
||||||
<LibraryBackgroundOverlay backgroundColor={background} />
|
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
<PlayButton
|
<PlayButton
|
||||||
|
|||||||
@@ -15,16 +15,8 @@ import { Stack } from '/@/shared/components/stack/stack';
|
|||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
|
import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface AlbumArtistDetailHeaderProps {
|
|
||||||
background: {
|
|
||||||
background?: string;
|
|
||||||
blur: number;
|
|
||||||
loading: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AlbumArtistDetailHeader = forwardRef(
|
export const AlbumArtistDetailHeader = forwardRef(
|
||||||
({ background }: AlbumArtistDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
(_props, ref: Ref<HTMLDivElement>) => {
|
||||||
const { albumArtistId, artistId } = useParams() as {
|
const { albumArtistId, artistId } = useParams() as {
|
||||||
albumArtistId?: string;
|
albumArtistId?: string;
|
||||||
artistId?: string;
|
artistId?: string;
|
||||||
@@ -95,7 +87,6 @@ export const AlbumArtistDetailHeader = forwardRef(
|
|||||||
item={{ route: AppRoute.LIBRARY_ALBUM_ARTISTS, type: LibraryItem.ALBUM_ARTIST }}
|
item={{ route: AppRoute.LIBRARY_ALBUM_ARTISTS, type: LibraryItem.ALBUM_ARTIST }}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
title={detailQuery?.data?.name || ''}
|
title={detailQuery?.data?.name || ''}
|
||||||
{...background}
|
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@@ -7,10 +7,14 @@ import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
|||||||
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 { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
|
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
|
||||||
|
import {
|
||||||
|
LibraryBackgroundImage,
|
||||||
|
LibraryBackgroundOverlay,
|
||||||
|
} from '/@/renderer/features/shared/components/library-background-overlay';
|
||||||
|
import { LibraryContainer } from '/@/renderer/features/shared/components/library-container';
|
||||||
import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar';
|
import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar';
|
||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer, useGeneralSettings } from '/@/renderer/store';
|
||||||
import { useGeneralSettings } from '/@/renderer/store/settings.store';
|
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
const AlbumArtistDetailRoute = () => {
|
const AlbumArtistDetailRoute = () => {
|
||||||
@@ -37,20 +41,20 @@ const AlbumArtistDetailRoute = () => {
|
|||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { background: backgroundColor, colorId } = useFastAverageColor({
|
const { background: backgroundColor } = useFastAverageColor({
|
||||||
id: artistId,
|
id: artistId,
|
||||||
src: detailQuery.data?.imageUrl,
|
src: detailQuery.data?.imageUrl,
|
||||||
srcLoaded: !detailQuery.isLoading,
|
srcLoaded: !detailQuery.isLoading,
|
||||||
});
|
});
|
||||||
|
|
||||||
const backgroundUrl = detailQuery.data?.imageUrl || '';
|
const background = backgroundColor;
|
||||||
const background = (artistBackground && `url(${backgroundUrl})`) || backgroundColor;
|
const showBlurredImage = Boolean(detailQuery.data?.imageUrl) && artistBackground;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPage key={`album-artist-detail-${routeId}`}>
|
<AnimatedPage key={`album-artist-detail-${routeId}`}>
|
||||||
<NativeScrollArea
|
<NativeScrollArea
|
||||||
pageHeaderProps={{
|
pageHeaderProps={{
|
||||||
backgroundColor: background,
|
backgroundColor: backgroundColor || undefined,
|
||||||
children: (
|
children: (
|
||||||
<LibraryHeaderBar>
|
<LibraryHeaderBar>
|
||||||
<LibraryHeaderBar.PlayButton
|
<LibraryHeaderBar.PlayButton
|
||||||
@@ -67,15 +71,19 @@ const AlbumArtistDetailRoute = () => {
|
|||||||
}}
|
}}
|
||||||
ref={scrollAreaRef}
|
ref={scrollAreaRef}
|
||||||
>
|
>
|
||||||
<AlbumArtistDetailHeader
|
{showBlurredImage && detailQuery.data?.imageUrl ? (
|
||||||
background={{
|
<LibraryBackgroundImage
|
||||||
background,
|
blur={artistBackgroundBlur}
|
||||||
blur: (artistBackground && artistBackgroundBlur) || 0,
|
headerRef={headerRef}
|
||||||
loading: !backgroundColor || colorId !== artistId,
|
imageUrl={detailQuery.data.imageUrl}
|
||||||
}}
|
/>
|
||||||
ref={headerRef}
|
) : (
|
||||||
/>
|
<LibraryBackgroundOverlay backgroundColor={background} headerRef={headerRef} />
|
||||||
<AlbumArtistDetailContent background={background} />
|
)}
|
||||||
|
<LibraryContainer>
|
||||||
|
<AlbumArtistDetailHeader ref={headerRef as React.Ref<HTMLDivElement>} />
|
||||||
|
<AlbumArtistDetailContent />
|
||||||
|
</LibraryContainer>
|
||||||
</NativeScrollArea>
|
</NativeScrollArea>
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,30 @@
|
|||||||
.root {
|
.overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20vh;
|
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
background-image: var(--theme-overlay-subheader);
|
background-image: var(--theme-overlay-subheader);
|
||||||
opacity: 0.3;
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-position: center !important;
|
||||||
|
background-size: cover !important;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-image-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--theme-overlay-subheader);
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,78 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import styles from './library-background-overlay.module.css';
|
import styles from './library-background-overlay.module.css';
|
||||||
|
|
||||||
interface LibraryBackgroundOverlayProps {
|
interface LibraryBackgroundOverlayProps {
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
|
headerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LibraryBackgroundOverlay = ({ backgroundColor }: LibraryBackgroundOverlayProps) => {
|
export const LibraryBackgroundOverlay = ({
|
||||||
return <div className={styles.root} style={{ backgroundColor }} />;
|
backgroundColor,
|
||||||
|
headerRef,
|
||||||
|
}: LibraryBackgroundOverlayProps) => {
|
||||||
|
const height = useHeaderHeight(headerRef);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.overlay}
|
||||||
|
style={{
|
||||||
|
backgroundColor,
|
||||||
|
height: height ? `${height + 64}px` : undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface LibraryBackgroundProps {
|
||||||
|
blur?: number;
|
||||||
|
headerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
|
imageUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LibraryBackgroundImage = ({ blur, headerRef, imageUrl }: LibraryBackgroundProps) => {
|
||||||
|
const url = `url(${imageUrl})`;
|
||||||
|
const height = useHeaderHeight(headerRef);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={styles.backgroundImage}
|
||||||
|
style={{
|
||||||
|
background: url,
|
||||||
|
filter: `blur(${blur ?? 0}rem)`,
|
||||||
|
height: height ? `${height - 64}px` : undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={styles.backgroundImageOverlay}
|
||||||
|
style={{
|
||||||
|
height: height ? `${height + 64}px` : undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useHeaderHeight = (headerRef: React.RefObject<HTMLDivElement | null>) => {
|
||||||
|
const [headerHeight, setHeaderHeight] = useState<number>(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!headerRef?.current) return;
|
||||||
|
|
||||||
|
const updateHeight = () => {
|
||||||
|
if (headerRef?.current) {
|
||||||
|
setHeaderHeight(headerRef.current.offsetHeight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateHeight();
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(updateHeight);
|
||||||
|
resizeObserver.observe(headerRef.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [headerRef]);
|
||||||
|
|
||||||
|
return headerHeight;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import styles from './library-container.module.css';
|
||||||
|
|
||||||
|
interface LibraryContainerProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LibraryContainer = ({ children }: LibraryContainerProps) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.content}>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -96,31 +96,6 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
z-index: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-position: center !important;
|
|
||||||
background-size: cover !important;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.background-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--theme-overlay-header);
|
|
||||||
}
|
|
||||||
|
|
||||||
.opaque-overlay {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { closeAllModals, openModal } from '@mantine/modals';
|
import { closeAllModals, openModal } from '@mantine/modals';
|
||||||
import { AutoTextSize } from 'auto-text-size';
|
import { AutoTextSize } from 'auto-text-size';
|
||||||
import clsx from 'clsx';
|
|
||||||
import { forwardRef, ReactNode, Ref, useCallback, useState } from 'react';
|
import { forwardRef, ReactNode, Ref, useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
@@ -11,7 +10,6 @@ import {
|
|||||||
WidePlayButton,
|
WidePlayButton,
|
||||||
WideShuffleButton,
|
WideShuffleButton,
|
||||||
} from '/@/renderer/features/shared/components/play-button';
|
} from '/@/renderer/features/shared/components/play-button';
|
||||||
import { useGeneralSettings } from '/@/renderer/store';
|
|
||||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { Center } from '/@/shared/components/center/center';
|
import { Center } from '/@/shared/components/center/center';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
@@ -21,8 +19,6 @@ import { Text } from '/@/shared/components/text/text';
|
|||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface LibraryHeaderProps {
|
interface LibraryHeaderProps {
|
||||||
background?: string;
|
|
||||||
blur?: number;
|
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
imagePlaceholderUrl?: null | string;
|
imagePlaceholderUrl?: null | string;
|
||||||
imageUrl?: null | string;
|
imageUrl?: null | string;
|
||||||
@@ -32,13 +28,9 @@ interface LibraryHeaderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LibraryHeader = forwardRef(
|
export const LibraryHeader = forwardRef(
|
||||||
(
|
({ children, imageUrl, item, title }: LibraryHeaderProps, ref: Ref<HTMLDivElement>) => {
|
||||||
{ background, blur, children, imageUrl, item, title }: LibraryHeaderProps,
|
|
||||||
ref: Ref<HTMLDivElement>,
|
|
||||||
) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isImageError, setIsImageError] = useState<boolean | null>(false);
|
const [isImageError, setIsImageError] = useState<boolean | null>(false);
|
||||||
const { albumBackground } = useGeneralSettings();
|
|
||||||
|
|
||||||
const onImageError = () => {
|
const onImageError = () => {
|
||||||
setIsImageError(true);
|
setIsImageError(true);
|
||||||
@@ -92,15 +84,6 @@ export const LibraryHeader = forwardRef(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.libraryHeader} ref={ref}>
|
<div className={styles.libraryHeader} ref={ref}>
|
||||||
<div
|
|
||||||
className={styles.background}
|
|
||||||
style={{ background, filter: `blur(${blur ?? 0}rem)` }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={clsx(styles.backgroundOverlay, {
|
|
||||||
[styles.opaqueOverlay]: albumBackground,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
className={styles.imageSection}
|
className={styles.imageSection}
|
||||||
onClick={() => openImage()}
|
onClick={() => openImage()}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ export const getFastAverageColor = async (args: {
|
|||||||
algorithm: args.algorithm || 'dominant',
|
algorithm: args.algorithm || 'dominant',
|
||||||
ignoredColor: [
|
ignoredColor: [
|
||||||
[255, 255, 255, 255, 90], // White
|
[255, 255, 255, 255, 90], // White
|
||||||
|
[255, 255, 255, 255, 50], // Light gray
|
||||||
|
[255, 255, 255, 255, 30], // Very light gray
|
||||||
|
[255, 255, 255, 255, 10], // Very very light gray
|
||||||
[0, 0, 0, 255, 30], // Black
|
[0, 0, 0, 255, 30], // Black
|
||||||
[0, 0, 0, 0, 40], // Transparent
|
[0, 0, 0, 0, 40], // Transparent
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user