quintodrome/ui/src/album/AlbumDetails.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

327 lines
8.2 KiB
JavaScript
Raw Normal View History

2020-11-27 12:02:02 -09:00
import React, { useMemo, useCallback } from 'react'
2020-11-13 17:11:24 -09:00
import {
Card,
CardContent,
CardMedia,
Collapse,
2021-07-17 19:29:52 -08:00
IconButton,
2021-07-24 15:52:15 -08:00
Tooltip,
2020-11-13 17:11:24 -09:00
makeStyles,
2020-11-27 12:02:02 -09:00
Typography,
2020-11-13 17:11:24 -09:00
useMediaQuery,
} from '@material-ui/core'
import {
useRecordContext,
useTranslate,
ArrayField,
SingleFieldList,
ChipField,
} from 'react-admin'
import clsx from 'clsx'
2020-08-21 08:41:23 -08:00
import Lightbox from 'react-image-lightbox'
import 'react-image-lightbox/style.css'
import subsonic from '../subsonic'
2020-11-13 17:11:24 -09:00
import {
ArtistLinkField,
2020-11-27 12:02:02 -09:00
DurationField,
2020-11-13 17:11:24 -09:00
formatRange,
2020-11-27 12:02:02 -09:00
SizeField,
LoveButton,
RatingField,
2020-11-13 17:11:24 -09:00
} from '../common'
import config from '../config'
import { intersperse } from '../utils'
2021-07-17 19:29:52 -08:00
import Link from '@material-ui/core/Link'
import MusicBrainz from '../icons/MusicBrainz'
import { ImLastfm2 } from 'react-icons/im'
const useStyles = makeStyles(
(theme) => ({
root: {
[theme.breakpoints.down('xs')]: {
padding: '0.7em',
minWidth: '20em',
},
[theme.breakpoints.up('sm')]: {
padding: '1em',
minWidth: '32em',
},
2020-11-13 17:11:24 -09:00
},
cardContents: {
display: 'flex',
2020-11-13 17:11:24 -09:00
},
details: {
display: 'flex',
flexDirection: 'column',
2020-11-13 17:11:24 -09:00
},
content: {
flex: '2 0 auto',
2020-11-13 17:11:24 -09:00
},
coverParent: {
[theme.breakpoints.down('xs')]: {
height: '8em',
width: '8em',
minWidth: '8em',
},
[theme.breakpoints.up('sm')]: {
height: '10em',
width: '10em',
minWidth: '10em',
},
[theme.breakpoints.up('lg')]: {
height: '15em',
width: '15em',
minWidth: '15em',
},
2020-11-13 17:11:24 -09:00
},
cover: {
objectFit: 'contain',
cursor: 'pointer',
display: 'block',
width: '100%',
height: '100%',
},
loveButton: {
top: theme.spacing(-0.2),
left: theme.spacing(0.5),
},
commentBlock: {
display: 'inline-block',
marginTop: '1em',
float: 'left',
wordBreak: 'break-all',
},
pointerCursor: {
cursor: 'pointer',
},
recordName: {},
recordArtist: {},
recordMeta: {},
genreList: {
2021-07-17 19:29:52 -08:00
marginTop: theme.spacing(0.5),
},
links: {
marginTop: theme.spacing(1.5),
},
}),
{
name: 'NDAlbumDetails',
}
)
2020-11-13 17:11:24 -09:00
2020-11-27 12:02:02 -09:00
const AlbumComment = ({ record }) => {
const classes = useStyles()
2020-11-13 17:11:24 -09:00
const [expanded, setExpanded] = React.useState(false)
2021-02-15 10:18:57 -09:00
const lines = record.comment.split('\n')
2020-11-27 12:02:02 -09:00
const formatted = useMemo(() => {
2021-02-15 10:18:57 -09:00
return lines.map((line, idx) => (
2021-02-02 14:00:06 -09:00
<span key={record.id + '-comment-' + idx}>
<span dangerouslySetInnerHTML={{ __html: line }} />
2020-11-27 12:02:02 -09:00
<br />
2021-02-02 14:00:06 -09:00
</span>
2020-11-27 12:02:02 -09:00
))
2021-02-15 10:18:57 -09:00
}, [lines, record.id])
2020-11-27 12:02:02 -09:00
const handleExpandClick = useCallback(() => {
setExpanded(!expanded)
}, [expanded, setExpanded])
2020-11-13 17:11:24 -09:00
return (
2020-11-27 12:02:02 -09:00
<Collapse
collapsedHeight={'1.5em'}
in={expanded}
timeout={'auto'}
className={clsx(
classes.commentBlock,
2021-02-15 10:18:57 -09:00
lines.length > 1 && classes.pointerCursor
)}
2020-11-27 12:02:02 -09:00
>
<Typography variant={'body1'} onClick={handleExpandClick}>
2020-11-27 12:02:02 -09:00
{formatted}
</Typography>
</Collapse>
2020-11-13 17:11:24 -09:00
)
}
const GenreList = () => {
2020-11-13 17:11:24 -09:00
const classes = useStyles()
return (
<ArrayField className={classes.genreList} source={'genres'}>
<SingleFieldList linkType={false}>
<ChipField source="name" />
</SingleFieldList>
</ArrayField>
)
}
2020-11-13 17:11:24 -09:00
const Details = (props) => {
const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs'))
const translate = useTranslate()
const record = useRecordContext(props)
let details = []
const addDetail = (obj) => {
const id = details.length
details.push(<span key={`detail-${record.id}-${id}`}>{obj}</span>)
}
const year = formatRange(record, 'year')
year && addDetail(<>{year}</>)
addDetail(
<>
{record.songCount +
' ' +
translate('resources.song.name', {
smart_count: record.songCount,
})}
</>
)
!isXsmall && addDetail(<DurationField source={'duration'} />)
!isXsmall && addDetail(<SizeField source="size" />)
return <>{intersperse(details, ' · ')}</>
}
2021-07-17 19:29:52 -08:00
const Links = (props) => {
const classes = useStyles()
const translate = useTranslate()
const record = useRecordContext(props)
let links = []
const addLink = (obj) => {
const id = links.length
links.push(<span key={`link-${record.id}-${id}`}>{obj}</span>)
}
addLink(
<Link
href={`https://last.fm/music/${
encodeURIComponent(record.albumArtist) +
'/' +
encodeURIComponent(record.name)
}`}
target="_blank"
rel="noopener noreferrer"
>
2021-07-24 15:52:15 -08:00
<Tooltip title={translate('message.openIn.lastfm')}>
<IconButton
size={'small'}
aria-label={translate('message.openIn.lastfm')}
>
<ImLastfm2 />
</IconButton>
</Tooltip>
2021-07-17 19:29:52 -08:00
</Link>
)
record.mbzAlbumId &&
addLink(
<Link
href={`https://musicbrainz.org/release/${record.mbzAlbumId}`}
target="_blank"
rel="noopener noreferrer"
>
2021-07-24 15:52:15 -08:00
<Tooltip title={translate('message.openIn.musicbrainz')}>
<IconButton
size={'small'}
aria-label={translate('message.openIn.musicbrainz')}
>
<MusicBrainz />
</IconButton>
</Tooltip>
2021-07-17 19:29:52 -08:00
</Link>
)
return <div className={classes.links}>{intersperse(links, ' ')}</div>
}
const AlbumDetails = (props) => {
const record = useRecordContext(props)
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('lg'))
const classes = useStyles()
const [isLightboxOpen, setLightboxOpen] = React.useState(false)
const imageUrl = subsonic.getCoverArtUrl(record, 300)
const fullImageUrl = subsonic.getCoverArtUrl(record)
2020-08-21 08:41:23 -08:00
const handleOpenLightbox = React.useCallback(() => setLightboxOpen(true), [])
const handleCloseLightbox = React.useCallback(
() => setLightboxOpen(false),
[]
)
return (
<Card className={classes.root}>
<div className={classes.cardContents}>
<div className={classes.coverParent}>
<CardMedia
component={'img'}
src={imageUrl}
width="400"
height="400"
className={classes.cover}
onClick={handleOpenLightbox}
title={record.name}
2020-11-13 17:11:24 -09:00
/>
</div>
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography
variant={isDesktop ? 'h5' : 'h6'}
className={classes.recordName}
>
{record.name}
{config.enableFavourites && (
<LoveButton
className={classes.loveButton}
record={record}
resource={'album'}
size={isDesktop ? 'default' : 'small'}
aria-label="love"
color="primary"
/>
)}
</Typography>
2021-07-17 19:29:52 -08:00
<Typography component={'h6'} className={classes.recordArtist}>
<ArtistLinkField record={record} />
</Typography>
2021-07-17 19:29:52 -08:00
<Typography component={'p'} className={classes.recordMeta}>
<Details />
</Typography>
{config.enableStarRating && (
<div>
<RatingField
record={record}
resource={'album'}
size={isDesktop ? 'medium' : 'small'}
/>
</div>
)}
2021-07-17 19:29:52 -08:00
{isDesktop ? (
<GenreList />
) : (
<Typography component={'p'}>{record.genre}</Typography>
)}
{isDesktop && (
<Typography component={'p'} className={classes.recordMeta}>
<Links />
</Typography>
)}
2020-11-27 12:02:02 -09:00
{isDesktop && record['comment'] && <AlbumComment record={record} />}
</CardContent>
</div>
</div>
2020-11-27 12:02:02 -09:00
{!isDesktop && record['comment'] && <AlbumComment record={record} />}
2020-08-21 08:41:23 -08:00
{isLightboxOpen && (
<Lightbox
imagePadding={50}
animationDuration={200}
imageTitle={record.name}
mainSrc={fullImageUrl}
2020-08-21 08:41:23 -08:00
onCloseRequest={handleCloseLightbox}
/>
)}
</Card>
)
}
2020-02-07 11:04:45 -09:00
export default AlbumDetails