Browse Source

Update album page

- Add additional album stats
- Add blurred header image

Add taglink, title color to album

Increase album view image size
master
jeffvli 3 years ago
committed by Jeff
parent
commit
03decf26c8
  1. 4
      src/components/layout/GenericPage.tsx
  2. 37
      src/components/layout/GenericPageHeader.tsx
  3. 1
      src/components/layout/Layout.tsx
  4. 80
      src/components/layout/styled.tsx
  5. 80
      src/components/library/AlbumView.tsx
  6. 13
      src/components/shared/TagLink.tsx
  7. 16
      src/components/shared/styled.ts
  8. 16
      src/shared/utils.ts

4
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) => {
<PageHeader
id="page-header"
padding={rest.padding}
style={{ paddingBottom: hideDivider && !rest.padding ? '20px' : '0px' }}
style={{ paddingBottom: hideDivider && !rest.padding ? '10px' : '0px' }}
>
{header}
</PageHeader>

37
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 = ({
<LazyLoadImage
src={image}
alt="header-img"
height={imageHeight || '145px'}
width={imageHeight || '145px'}
height={imageHeight || '195px'}
width={imageHeight || '195px'}
visibleByDefault
afterLoad={() => {
if (cacheImages.enabled) {
@ -62,13 +68,7 @@ const GenericPageHeader = ({
</CoverArtWrapper>
)}
<div
style={{
display: image ? 'inline-block' : 'undefined',
width: image ? 'calc(100% - 160px)' : '100%',
marginLeft: image ? '15px' : '0px',
}}
>
<PageHeaderWrapper isDark={isDark} hasImage={image} imageHeight={imageHeight || 195}>
<div
style={{
display: 'flex',
@ -104,6 +104,7 @@ const GenericPageHeader = ({
<Icon icon="search" />
</InputGroup.Addon>
<StyledInput
opacity={0.6}
id="local-search-input"
value={searchQuery}
onChange={handleSearch}
@ -160,18 +161,8 @@ const GenericPageHeader = ({
height: '50%',
}}
>
<span
style={{
alignSelf: 'center',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
width: '60%',
}}
>
{subtitle}
</span>
<span style={{ alignSelf: 'center' }}>
<PageHeaderSubtitleWrapper>{subtitle}</PageHeaderSubtitleWrapper>
<span style={{ alignSelf: 'flex-end' }}>
{subsidetitle && <span style={{ display: 'inline-block' }}>{subsidetitle}</span>}
{showViewTypeButtons && (
<span style={{ display: 'inline-block' }}>
@ -184,7 +175,7 @@ const GenericPageHeader = ({
)}
</span>
</div>
</div>
</PageHeaderWrapper>
</>
);
};

1
src/components/layout/Layout.tsx

@ -107,6 +107,7 @@ const Layout = ({ footer, children, disableSidebar, font }: any) => {
<FlexboxGrid
justify="space-between"
style={{
zIndex: 2,
padding: '0 10px 0 10px',
margin: '10px 5px 5px 5px',
}}

80
src/components/layout/styled.tsx

@ -156,6 +156,7 @@ export const PageHeader = styled(Header)<{ padding?: string }>`
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;
`;

80
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,10 +182,24 @@ const AlbumView = ({ ...rest }: any) => {
}
return (
<>
{!rest.isModal && (
<BlurredBackgroundWrapper
image={!data?.image.match('placeholder') ? data.image : null}
expanded={misc.expandSidebar}
>
<BlurredBackground
image={!data?.image.match('placeholder') ? data.image : null}
expanded={misc.expandSidebar}
/>
</BlurredBackgroundWrapper>
)}
<GenericPage
hideDivider
header={
<GenericPageHeader
isDark={!rest.isModal}
image={
isCached(`${misc.imageCachePath}album_${albumId}.jpg`)
? `${misc.imageCachePath}album_${albumId}.jpg`
@ -185,20 +210,29 @@ const AlbumView = ({ ...rest }: any) => {
cacheType: 'album',
id: data.albumId,
}}
imageHeight={200}
title={data.name}
showTitleTooltip
subtitle={
<div>
<div
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
<PageHeaderSubtitleDataLine $top>
<strong>ALBUM</strong> {' • '} {data.songCount} songs,{' '}
{formatDuration(data.duration)}
{data.year && (
<>
{' • '}
{data.year}
</>
)}
</PageHeaderSubtitleDataLine>
<PageHeaderSubtitleDataLine>
Added {formatDate(data.created)}
</PageHeaderSubtitleDataLine>
<PageHeaderSubtitleDataLine>
{data.artist && (
<StyledLink
<StyledTagLink
tabIndex={0}
tooltip={data.artist}
onClick={() => {
if (!rest.isModal) {
history.push(`/library/artist/${data.artistId}`);
@ -228,13 +262,13 @@ const AlbumView = ({ ...rest }: any) => {
}}
>
{data.artist}
</StyledLink>
</StyledTagLink>
)}
{data.genre && (
<>
{' • '}
<StyledLink
<StyledTagLink
tabIndex={0}
tooltip={data.genre}
onClick={() => {
if (!rest.isModal) {
dispatch(setActive({ ...album.active, filter: data.genre }));
@ -270,31 +304,24 @@ const AlbumView = ({ ...rest }: any) => {
}}
>
{data.genre}
</StyledLink>
</>
)}
{data.year && (
<>
{' • '}
{data.year}
</StyledTagLink>
</>
)}
</div>
</PageHeaderSubtitleDataLine>
<div style={{ marginTop: '10px' }}>
<ButtonToolbar>
<PlayButton appearance="primary" size="md" onClick={handlePlay} />
<PlayButton appearance="primary" size="lg" onClick={handlePlay} />
<PlayAppendNextButton
appearance="primary"
size="md"
size="lg"
onClick={() => handlePlayAppend('next')}
/>
<PlayAppendButton
appearance="primary"
size="md"
size="lg"
onClick={() => handlePlayAppend('later')}
/>
<FavoriteButton size="md" isFavorite={data.starred} onClick={handleFavorite} />
<FavoriteButton size="lg" isFavorite={data.starred} onClick={handleFavorite} />
</ButtonToolbar>
</div>
</div>
@ -331,6 +358,7 @@ const AlbumView = ({ ...rest }: any) => {
handleFavorite={handleRowFavorite}
/>
</GenericPage>
</>
);
};

13
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 (
<CustomTooltip text={tooltip}>
<Tag {...rest}>{children}</Tag>
</CustomTooltip>
);
};
export default TagLink;

16
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;
`;

16
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');
};

Loading…
Cancel
Save