diff --git a/src/renderer/features/albums/components/album-detail-header.tsx b/src/renderer/features/albums/components/album-detail-header.tsx index 1c4a7592..c768a155 100644 --- a/src/renderer/features/albums/components/album-detail-header.tsx +++ b/src/renderer/features/albums/components/album-detail-header.tsx @@ -1,72 +1,14 @@ -import { Center, Group } from '@mantine/core'; -import { useMergedRef } from '@mantine/hooks'; +import { Group, Stack } from '@mantine/core'; import { forwardRef, Fragment, Ref } from 'react'; -import { RiAlbumFill } from 'react-icons/ri'; import { generatePath, useParams } from 'react-router'; import { Link } from 'react-router-dom'; -import styled from 'styled-components'; -import { Text, TextTitle } from '/@/renderer/components'; +import { Text } from '/@/renderer/components'; import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query'; +import { LibraryHeader } from '/@/renderer/features/shared'; import { useContainerQuery } from '/@/renderer/hooks'; import { AppRoute } from '/@/renderer/router/routes'; - -const HeaderContainer = styled.div` - position: relative; - display: grid; - grid-auto-columns: 1fr; - grid-template-areas: 'image info'; - grid-template-rows: 1fr; - grid-template-columns: 250px minmax(0, 1fr); - gap: 0.5rem; - width: 100%; - max-width: 100%; - height: 30vh; - min-height: 340px; - max-height: 500px; - padding: 5rem 2rem 2rem; -`; - -const CoverImageWrapper = styled.div` - z-index: 15; - display: flex; - grid-area: image; - align-items: flex-end; - justify-content: center; - height: 100%; - filter: drop-shadow(0 0 8px rgb(0, 0, 0, 50%)); -`; - -const MetadataWrapper = styled.div` - z-index: 15; - display: flex; - flex-direction: column; - grid-area: info; - justify-content: flex-end; - width: 100%; -`; - -const StyledImage = styled.img` - object-fit: cover; -`; - -const BackgroundImage = styled.div<{ background: string }>` - position: absolute; - top: 0; - z-index: 0; - width: 100%; - height: 100%; - background: ${(props) => props.background}; -`; - -const BackgroundImageOverlay = styled.div` - position: absolute; - top: 0; - left: 0; - z-index: 0; - width: 100%; - height: 100%; - background: linear-gradient(180deg, rgba(25, 26, 28, 5%), var(--main-bg)), var(--background-noise); -`; +import { LibraryItem } from '/@/renderer/types'; +import { formatDurationString } from '/@/renderer/utils'; interface AlbumDetailHeaderProps { background: string; @@ -77,110 +19,81 @@ export const AlbumDetailHeader = forwardRef( const { albumId } = useParams() as { albumId: string }; const detailQuery = useAlbumDetail({ id: albumId }); const cq = useContainerQuery(); - const mergedRef = useMergedRef(ref, cq.ref); - const titleSize = cq.isXl - ? '6rem' - : cq.isLg - ? '5.5rem' - : cq.isMd - ? '4.5rem' - : cq.isSm - ? '3.5rem' - : '2rem'; + const metadataItems = [ + { + id: 'releaseYear', + secondary: false, + value: detailQuery?.data?.releaseYear, + }, + { + id: 'songCount', + secondary: false, + value: `${detailQuery?.data?.songCount} songs`, + }, + { + id: 'duration', + secondary: true, + value: detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration), + }, + ]; + + console.log('detailQuery?.data?.duration :>> ', detailQuery?.data?.duration); return ( - - - - - {detailQuery?.data?.imageUrl ? ( - - ) : ( -
+ + + + {metadataItems.map((item, index) => ( + + {index > 0 && } + {item.value} + + ))} + + - -
- )} -
- - - - Album - - {detailQuery?.data?.releaseYear && ( - <> - - {detailQuery?.data?.releaseYear} - - )} - - - {detailQuery?.data?.name} - - - {detailQuery?.data?.albumArtists.map((artist, index) => ( - - {index > 0 && ( + {detailQuery?.data?.albumArtists.map((artist, index) => ( + + {index > 0 && ( + + • + + )} - • + {artist.name} - )} - - {artist.name} - - - ))} - - -
+ + ))} + + + + ); }, ); diff --git a/src/renderer/features/albums/routes/album-detail-route.tsx b/src/renderer/features/albums/routes/album-detail-route.tsx index 91c704c3..3457dd96 100644 --- a/src/renderer/features/albums/routes/album-detail-route.tsx +++ b/src/renderer/features/albums/routes/album-detail-route.tsx @@ -1,65 +1,60 @@ -import { PageHeader, ScrollArea, TextTitle } from '/@/renderer/components'; -import { AnimatedPage, PlayButton } from '/@/renderer/features/shared'; +import { NativeScrollArea } from '/@/renderer/components'; +import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared'; import { useRef } from 'react'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query'; import { useParams } from 'react-router'; -import { useFastAverageColor, useShouldPadTitlebar } from '/@/renderer/hooks'; +import { useFastAverageColor } from '/@/renderer/hooks'; import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content'; import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header'; -import { useIntersection } from '@mantine/hooks'; -import { Group } from '@mantine/core'; +import { usePlayQueueAdd } from '/@/renderer/features/player'; +import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; +import { LibraryItem } from '/@/renderer/types'; const AlbumDetailRoute = () => { const tableRef = useRef(null); + const scrollAreaRef = useRef(null); + const headerRef = useRef(null); + const { albumId } = useParams() as { albumId: string }; const detailQuery = useAlbumDetail({ id: albumId }); const background = useFastAverageColor(detailQuery.data?.imageUrl); - const padTop = useShouldPadTitlebar(); + const handlePlayQueueAdd = usePlayQueueAdd(); + const playButtonBehavior = usePlayButtonBehavior(); - const containerRef = useRef(); - const { ref, entry } = useIntersection({ - root: containerRef.current, - threshold: 0, - }); + const handlePlay = () => { + handlePlayQueueAdd?.({ + byItemType: { + id: [albumId], + type: LibraryItem.ALBUM, + }, + play: playButtonBehavior, + }); + }; + + if (!background) return null; return ( - + + {detailQuery?.data?.name} + + ), + target: headerRef, + }} > - - - - {detailQuery?.data?.name} - - - - - - {background && ( - <> - - - - )} - + + + ); }; diff --git a/src/renderer/features/playlists/components/playlist-detail-header.tsx b/src/renderer/features/playlists/components/playlist-detail-header.tsx index 845c1b62..24f85ff3 100644 --- a/src/renderer/features/playlists/components/playlist-detail-header.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-header.tsx @@ -1,4 +1,5 @@ import { Group, Stack } from '@mantine/core'; +import { forwardRef, Ref } from 'react'; import { RiMoreFill } from 'react-icons/ri'; import { generatePath, useParams } from 'react-router'; import { Link } from 'react-router-dom'; @@ -16,77 +17,79 @@ interface PlaylistDetailHeaderProps { imageUrl?: string | null; } -export const PlaylistDetailHeader = ({ - background, - imageUrl, - imagePlaceholderUrl, -}: PlaylistDetailHeaderProps) => { - const { playlistId } = useParams() as { playlistId: string }; - const detailQuery = usePlaylistDetail({ id: playlistId }); - const handlePlayQueueAdd = usePlayQueueAdd(); - const playButtonBehavior = usePlayButtonBehavior(); +export const PlaylistDetailHeader = forwardRef( + ( + { background, imageUrl, imagePlaceholderUrl }: PlaylistDetailHeaderProps, + ref: Ref, + ) => { + const { playlistId } = useParams() as { playlistId: string }; + const detailQuery = usePlaylistDetail({ id: playlistId }); + const handlePlayQueueAdd = usePlayQueueAdd(); + const playButtonBehavior = usePlayButtonBehavior(); - const handlePlay = (playType?: Play) => { - handlePlayQueueAdd?.({ - byItemType: { - id: [playlistId], - type: LibraryItem.PLAYLIST, - }, - play: playType || playButtonBehavior, - }); - }; + const handlePlay = (playType?: Play) => { + handlePlayQueueAdd?.({ + byItemType: { + id: [playlistId], + type: LibraryItem.PLAYLIST, + }, + play: playType || playButtonBehavior, + }); + }; - return ( - - - - - - - - - handlePlay()} /> - - - - - - {PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map((type) => ( - handlePlay(type.play)} + return ( + + + + + + + + + handlePlay()} /> + + + + + + {PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map((type) => ( + handlePlay(type.play)} + > + {type.label} + + ))} + + Edit playlist + + + - - - ); -}; + + ); + }, +); diff --git a/src/renderer/features/playlists/routes/playlist-detail-route.tsx b/src/renderer/features/playlists/routes/playlist-detail-route.tsx index 7a64871f..d4f36740 100644 --- a/src/renderer/features/playlists/routes/playlist-detail-route.tsx +++ b/src/renderer/features/playlists/routes/playlist-detail-route.tsx @@ -1,41 +1,64 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import { useRef } from 'react'; import { useParams } from 'react-router'; -import { PageHeader, ScrollArea } from '/@/renderer/components'; +import { NativeScrollArea } from '/@/renderer/components'; +import { usePlayQueueAdd } from '/@/renderer/features/player'; import { PlaylistDetailContent } from '/@/renderer/features/playlists/components/playlist-detail-content'; import { PlaylistDetailHeader } from '/@/renderer/features/playlists/components/playlist-detail-header'; import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query'; -import { AnimatedPage } from '/@/renderer/features/shared'; -import { useFastAverageColor, useShouldPadTitlebar } from '/@/renderer/hooks'; +import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared'; +import { useFastAverageColor } from '/@/renderer/hooks'; +import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; +import { LibraryItem } from '/@/renderer/types'; const PlaylistDetailRoute = () => { const tableRef = useRef(null); + const scrollAreaRef = useRef(null); + const headerRef = useRef(null); const { playlistId } = useParams() as { playlistId: string }; - const padTitlebar = useShouldPadTitlebar(); const detailQuery = usePlaylistDetail({ id: playlistId }); const background = useFastAverageColor(detailQuery?.data?.imageUrl, 'dominant'); + const handlePlayQueueAdd = usePlayQueueAdd(); + const playButtonBehavior = usePlayButtonBehavior(); + + const handlePlay = () => { + handlePlayQueueAdd?.({ + byItemType: { + id: [playlistId], + type: LibraryItem.PLAYLIST, + }, + play: playButtonBehavior, + }); + }; + + if (!background) return null; + return ( - <> - - {background && ( - - - - - - - )} - + + + + {detailQuery?.data?.name} + + ), + target: headerRef, + }} + > + + + + ); };