From 03decf26c8a1b75f81b2e90a384f573eec9e3d97 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Wed, 3 Nov 2021 20:22:31 -0700 Subject: [PATCH] Update album page - Add additional album stats - Add blurred header image Add taglink, title color to album Increase album view image size --- src/components/layout/GenericPage.tsx | 4 +- src/components/layout/GenericPageHeader.tsx | 37 +-- src/components/layout/Layout.tsx | 1 + src/components/layout/styled.tsx | 80 +++++- src/components/library/AlbumView.tsx | 292 +++++++++++--------- src/components/shared/TagLink.tsx | 13 + src/components/shared/styled.ts | 16 +- src/shared/utils.ts | 16 ++ 8 files changed, 300 insertions(+), 159 deletions(-) create mode 100644 src/components/shared/TagLink.tsx diff --git a/src/components/layout/GenericPage.tsx b/src/components/layout/GenericPage.tsx index 2c944ca..28b4734 100644 --- a/src/components/layout/GenericPage.tsx +++ b/src/components/layout/GenericPage.tsx @@ -34,7 +34,7 @@ const GenericPage = ({ header, children, hideDivider, ...rest }: any) => { id="page-container" $backgroundSrc={ misc.dynamicBackground - ? !backgroundImage.match('placeholder') + ? !backgroundImage?.match('placeholder') ? backgroundImage : undefined : undefined @@ -43,7 +43,7 @@ const GenericPage = ({ header, children, hideDivider, ...rest }: any) => { {header} diff --git a/src/components/layout/GenericPageHeader.tsx b/src/components/layout/GenericPageHeader.tsx index 38c9ea1..58f98c5 100644 --- a/src/components/layout/GenericPageHeader.tsx +++ b/src/components/layout/GenericPageHeader.tsx @@ -10,7 +10,12 @@ import { StyledInputGroup, StyledInputGroupButton, } from '../shared/styled'; -import { CoverArtWrapper, PageHeaderTitle } from './styled'; +import { + CoverArtWrapper, + PageHeaderSubtitleWrapper, + PageHeaderTitle, + PageHeaderWrapper, +} from './styled'; import cacheImage from '../shared/cacheImage'; import CustomTooltip from '../shared/CustomTooltip'; @@ -31,6 +36,7 @@ const GenericPageHeader = ({ viewTypeSetting, cacheImages, showTitleTooltip, + isDark, }: any) => { const history = useHistory(); const [openSearch, setOpenSearch] = useState(false); @@ -47,8 +53,8 @@ const GenericPageHeader = ({ { if (cacheImages.enabled) { @@ -62,13 +68,7 @@ const GenericPageHeader = ({ )} -
+
- - {subtitle} - - + {subtitle} + {subsidetitle && {subsidetitle}} {showViewTypeButtons && ( @@ -184,7 +175,7 @@ const GenericPageHeader = ({ )}
-
+ ); }; diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index 8185ea3..7ba169f 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -107,6 +107,7 @@ const Layout = ({ footer, children, disableSidebar, font }: any) => { ` export const PageContent = styled(Content)<{ padding?: string }>` position: relative; padding: ${(props) => (props.padding ? props.padding : '10px')}; + z-index: 1; `; // Sidebar.tsx @@ -205,5 +206,82 @@ export const PageHeaderTitle = styled.h1` text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - font-size: ${(props) => props.theme.fonts.size.pageTitle}; + font-size: 4vw; + + @media screen and (min-width: 1280px) { + font-size: 48px; + } +`; + +export const PageHeaderWrapper = styled.div<{ $hasImage: boolean; $imageHeight: string }>` + display: ${(props) => (props.$hasImage ? 'inline-block' : 'undefined')}; + width: ${(props) => (props.$hasImage ? `calc(100% - ${props.$imageHeight + 15}px)` : '100%')}; + margin-left: ${(props) => (props.$hasImage ? '15px' : '0px')}; + vertical-align: top; + color: ${(props) => (props.$hasImage ? '#D8D8D8' : props.theme.colors.layout.page.color)}; +`; + +export const PageHeaderSubtitleWrapper = styled.span` + align-self: center; + width: 70%; + font-size: 14px; +`; + +export const PageHeaderSubtitleDataLine = styled.div<{ $top?: boolean }>` + margin-top: ${(props) => (props.$top ? '0px' : '10px')}; +`; + +export const FlatBackground = styled.div<{ $expanded: boolean; $color: string }>` + background: ${(props) => props.$color}; + top: 32px; + left: ${(props) => (props.$expanded ? '165px' : '56px')}; + height: 200px; + position: absolute; + width: ${(props) => (props.$expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')}; + z-index: 1; + user-select: none; + pointer-events: none; +`; + +export const BlurredBackgroundWrapper = styled.div<{ $expanded: boolean }>` + clip: rect(0, auto, auto, 0); + -webkit-clip-path: inset(0 0); + clip-path: inset(0 0); + position: absolute; + left: ${(props) => (props.$expanded ? '165px' : '56px')}; + width: ${(props) => (props.$expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')}; + top: 32px; + z-index: 1; + display: block; + background: #0b0908; +`; + +export const BlurredBackground = styled.img<{ $expanded: boolean; $image: string }>` + background-image: ${(props) => `url(${props.$image})`}; + background-position: center 30%; + background-size: cover; + filter: blur(10px) brightness(0.4); + + outline: none !important; + border: none !important; + margin: 0px !important; + padding: 0px !important; + width: 100%; + height: 202px; + z-index: -1; + user-select: none; + pointer-events: none; + display: block; +`; + +export const GradientBackground = styled.div<{ $expanded: boolean; $color: string }>` + background: ${(props) => `linear-gradient(0deg, transparent 10%, ${props.$color} 100%)`}; + top: 32px; + left: ${(props) => (props.$expanded ? '165px' : '56px')}; + height: calc(100% - 130px); + position: absolute; + width: ${(props) => (props.$expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')}; + z-index: 1; + user-select: none; + pointer-events: none; `; diff --git a/src/components/library/AlbumView.tsx b/src/components/library/AlbumView.tsx index a3e3941..b7ff1fb 100644 --- a/src/components/library/AlbumView.tsx +++ b/src/components/library/AlbumView.tsx @@ -34,9 +34,20 @@ import GenericPageHeader from '../layout/GenericPageHeader'; import { setStatus } from '../../redux/playerSlice'; import { addModalPage } from '../../redux/miscSlice'; import { notifyToast } from '../shared/toast'; -import { filterPlayQueue, getPlayedSongsNotification, isCached } from '../../shared/utils'; -import { StyledLink } from '../shared/styled'; +import { + filterPlayQueue, + formatDate, + formatDuration, + getPlayedSongsNotification, + isCached, +} from '../../shared/utils'; +import { StyledTagLink } from '../shared/styled'; import { setActive } from '../../redux/albumSlice'; +import { + BlurredBackground, + BlurredBackgroundWrapper, + PageHeaderSubtitleDataLine, +} from '../layout/styled'; interface AlbumParams { id: string; @@ -171,76 +182,60 @@ const AlbumView = ({ ...rest }: any) => { } return ( - -
- {data.artist && ( - { - if (!rest.isModal) { - history.push(`/library/artist/${data.artistId}`); - } else { - dispatch( - addModalPage({ - pageType: 'artist', - id: data.artistId, - }) - ); - } - }} - onKeyDown={(e: any) => { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - if (!rest.isModal) { - history.push(`/library/artist/${data.artistId}`); - } else { - dispatch( - addModalPage({ - pageType: 'artist', - id: data.artistId, - }) - ); - } - } - }} - > - {data.artist} - - )} - {data.genre && ( - <> - {' • '} - + {!rest.isModal && ( + + + + )} + + + + ALBUM {' • '} {data.songCount} songs,{' '} + {formatDuration(data.duration)} + {data.year && ( + <> + {' • '} + {data.year} + + )} + + + Added {formatDate(data.created)} + + + {data.artist && ( + { if (!rest.isModal) { - dispatch(setActive({ ...album.active, filter: data.genre })); - setTimeout(() => { - history.push(`/library/album?sortType=${data.genre}`); - }, 50); + history.push(`/library/artist/${data.artistId}`); } else { dispatch( addModalPage({ @@ -253,6 +248,28 @@ const AlbumView = ({ ...rest }: any) => { onKeyDown={(e: any) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); + if (!rest.isModal) { + history.push(`/library/artist/${data.artistId}`); + } else { + dispatch( + addModalPage({ + pageType: 'artist', + id: data.artistId, + }) + ); + } + } + }} + > + {data.artist} + + )} + {data.genre && ( + <> + { if (!rest.isModal) { dispatch(setActive({ ...album.active, filter: data.genre })); setTimeout(() => { @@ -266,71 +283,82 @@ const AlbumView = ({ ...rest }: any) => { }) ); } - } - }} - > - {data.genre} - - - )} - - {data.year && ( - <> - {' • '} - {data.year} - - )} + }} + onKeyDown={(e: any) => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + if (!rest.isModal) { + dispatch(setActive({ ...album.active, filter: data.genre })); + setTimeout(() => { + history.push(`/library/album?sortType=${data.genre}`); + }, 50); + } else { + dispatch( + addModalPage({ + pageType: 'artist', + id: data.artistId, + }) + ); + } + } + }} + > + {data.genre} + + + )} + +
+ + + handlePlayAppend('next')} + /> + handlePlayAppend('later')} + /> + + +
-
- - - handlePlayAppend('next')} - /> - handlePlayAppend('later')} - /> - - -
- - } - searchQuery={searchQuery} - handleSearch={(e: any) => setSearchQuery(e)} - clearSearchQuery={() => setSearchQuery('')} - showSearchBar + } + searchQuery={searchQuery} + handleSearch={(e: any) => setSearchQuery(e)} + clearSearchQuery={() => setSearchQuery('')} + showSearchBar + /> + } + > + - } - > - -
+ + ); }; diff --git a/src/components/shared/TagLink.tsx b/src/components/shared/TagLink.tsx new file mode 100644 index 0000000..5b128f0 --- /dev/null +++ b/src/components/shared/TagLink.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Tag } from 'rsuite'; +import CustomTooltip from './CustomTooltip'; + +const TagLink = ({ tooltip, children, ...rest }: any) => { + return ( + + {children} + + ); +}; + +export default TagLink; diff --git a/src/components/shared/styled.ts b/src/components/shared/styled.ts index debef85..3bdb2c3 100644 --- a/src/components/shared/styled.ts +++ b/src/components/shared/styled.ts @@ -17,6 +17,7 @@ import { Tag, } from 'rsuite'; import styled from 'styled-components'; +import TagLink from './TagLink'; export const HeaderButton = styled(Button)` margin-left: 5px; @@ -140,7 +141,7 @@ export const StyledInputNumber = styled(InputNumber)<{ width: number }>` width: ${(props) => `${props.width}px`}; `; -export const StyledInput = styled(Input)<{ width: number }>` +export const StyledInput = styled(Input)<{ width: number; opacity?: number }>` border: 1px #3c3f43 solid !important; border-radius: ${(props) => props.theme.other.input.borderRadius} !important; @@ -148,6 +149,7 @@ export const StyledInput = styled(Input)<{ width: number }>` background: ${(props) => props.theme.colors.input.background} !important; width: ${(props) => `${props.width}px`}; border-radius: ${(props) => props.theme.other.input.borderRadius}; + opacity: ${(props) => props.opacity}; `; export const StyledCheckbox = styled(Checkbox)` @@ -512,5 +514,17 @@ export const StyledTag = styled(Tag)` color: ${(props) => props.theme.colors.tag.text} !important; background: ${(props) => props.theme.colors.tag.background}; border-radius: ${(props) => props.theme.other.tag.borderRadius}; + + cursor: pointer; +`; + +export const StyledTagLink = styled(TagLink)` + color: ${(props) => props.theme.colors.tag.text} !important; + background: ${(props) => props.theme.colors.tag.background}; + border-radius: ${(props) => props.theme.other.tag.borderRadius}; + + max-width: 13rem; + text-overflow: ellipsis; + overflow: hidden; cursor: pointer; `; diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 042ccaf..86f9852 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -118,6 +118,22 @@ export const formatSongDuration = (duration: number) => { return `${minutes}:${seconds}`; }; +export const formatDuration = (duration: number) => { + const hours = Math.floor(duration / 60 / 60); + const minutes = Math.floor((duration / 60) % 60); + const seconds = String(duration % 60).padStart(2, '0'); + + if (hours > 0) { + return `${hours} hr ${minutes} min ${seconds} sec`; + } + + if (Number.isNaN(minutes)) { + return null; + } + + return `${minutes} min ${seconds} sec`; +}; + export const formatDate = (date: string) => { return moment(date).format('MMM D YYYY'); };