Add Album comment to Album details
This commit is contained in:
@@ -1,15 +1,170 @@
|
|||||||
import React from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { Card, CardContent, CardMedia, Typography } from '@material-ui/core'
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardMedia,
|
||||||
|
Typography,
|
||||||
|
Collapse,
|
||||||
|
makeStyles,
|
||||||
|
IconButton,
|
||||||
|
Fab,
|
||||||
|
useMediaQuery,
|
||||||
|
} from '@material-ui/core'
|
||||||
|
import classnames from 'classnames'
|
||||||
import { useTranslate } from 'react-admin'
|
import { useTranslate } from 'react-admin'
|
||||||
import Lightbox from 'react-image-lightbox'
|
import Lightbox from 'react-image-lightbox'
|
||||||
import 'react-image-lightbox/style.css'
|
import 'react-image-lightbox/style.css'
|
||||||
|
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
|
||||||
import subsonic from '../subsonic'
|
import subsonic from '../subsonic'
|
||||||
import { DurationField, StarButton, SizeField } from '../common'
|
import {
|
||||||
import { ArtistLinkField, formatRange } from '../common'
|
DurationField,
|
||||||
|
StarButton,
|
||||||
|
SizeField,
|
||||||
|
ArtistLinkField,
|
||||||
|
formatRange,
|
||||||
|
MultiLineTextField,
|
||||||
|
} from '../common'
|
||||||
|
|
||||||
const AlbumDetails = ({ classes, record }) => {
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
container: {
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
padding: '0.7em',
|
||||||
|
minWidth: '24em',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
padding: '1em',
|
||||||
|
minWidth: '32em',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
starButton: {
|
||||||
|
bottom: theme.spacing(-1.5),
|
||||||
|
right: theme.spacing(-1.5),
|
||||||
|
},
|
||||||
|
albumCover: {
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
cursor: 'pointer',
|
||||||
|
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
height: '8em',
|
||||||
|
width: '8em',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
height: '10em',
|
||||||
|
width: '10em',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
height: '15em',
|
||||||
|
width: '15em',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
albumDetails: {
|
||||||
|
display: 'inline-block',
|
||||||
|
verticalAlign: 'top',
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
width: '14em',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
width: '26em',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
width: '43em',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
albumTitle: {
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
marginTop: '1em',
|
||||||
|
display: 'inline-block',
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
width: '10em',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
width: '10em',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
width: '10em',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
commentFirstLine: {
|
||||||
|
float: 'left',
|
||||||
|
marginRight: '5px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
},
|
||||||
|
expand: {
|
||||||
|
marginTop: '-5px',
|
||||||
|
transform: 'rotate(0deg)',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
transition: theme.transitions.create('transform', {
|
||||||
|
duration: theme.transitions.duration.shortest,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expandOpen: {
|
||||||
|
transform: 'rotate(180deg)',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const AlbumComment = ({ classes, record, commentNumLines }) => {
|
||||||
|
const [expanded, setExpanded] = React.useState(false)
|
||||||
|
|
||||||
|
const handleExpandClick = React.useCallback(() => {
|
||||||
|
commentNumLines > 1 && setExpanded(!expanded)
|
||||||
|
}, [expanded, setExpanded, commentNumLines])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.comment}>
|
||||||
|
<div onClick={handleExpandClick}>
|
||||||
|
<MultiLineTextField
|
||||||
|
record={record}
|
||||||
|
source={'comment'}
|
||||||
|
maxLines={1}
|
||||||
|
className={classes.commentFirstLine}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{commentNumLines > 1 && (
|
||||||
|
<IconButton
|
||||||
|
size={'small'}
|
||||||
|
className={classnames(classes.expand, {
|
||||||
|
[classes.expandOpen]: expanded,
|
||||||
|
})}
|
||||||
|
onClick={handleExpandClick}
|
||||||
|
aria-expanded={expanded}
|
||||||
|
aria-label="show more"
|
||||||
|
>
|
||||||
|
<ExpandMoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||||
|
<MultiLineTextField
|
||||||
|
record={record}
|
||||||
|
source={'comment'}
|
||||||
|
firstLine={1}
|
||||||
|
className={classes.commentFirstLine}
|
||||||
|
/>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const AlbumDetails = ({ record }) => {
|
||||||
|
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('lg'))
|
||||||
|
const classes = useStyles()
|
||||||
const [isLightboxOpen, setLightboxOpen] = React.useState(false)
|
const [isLightboxOpen, setLightboxOpen] = React.useState(false)
|
||||||
const translate = useTranslate()
|
const translate = useTranslate()
|
||||||
|
|
||||||
|
const commentNumLines = useMemo(
|
||||||
|
() => record.comment && record.comment.split('\n').length,
|
||||||
|
[record]
|
||||||
|
)
|
||||||
|
|
||||||
const genreYear = (record) => {
|
const genreYear = (record) => {
|
||||||
let genreDateLine = []
|
let genreDateLine = []
|
||||||
if (record.genre) {
|
if (record.genre) {
|
||||||
@@ -38,14 +193,23 @@ const AlbumDetails = ({ classes, record }) => {
|
|||||||
() => setLightboxOpen(false),
|
() => setLightboxOpen(false),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={classes.container}>
|
<Card className={classes.container}>
|
||||||
<CardMedia
|
<CardMedia
|
||||||
image={imageUrl}
|
image={imageUrl}
|
||||||
className={classes.albumCover}
|
className={classes.albumCover}
|
||||||
onClick={handleOpenLightbox}
|
onClick={handleOpenLightbox}
|
||||||
></CardMedia>
|
>
|
||||||
|
<StarButton
|
||||||
|
className={classes.starButton}
|
||||||
|
record={record}
|
||||||
|
resource={'album'}
|
||||||
|
size={isDesktop ? 'default' : 'small'}
|
||||||
|
aria-label="star"
|
||||||
|
color="primary"
|
||||||
|
component={Fab}
|
||||||
|
/>
|
||||||
|
</CardMedia>
|
||||||
<CardContent className={classes.albumDetails}>
|
<CardContent className={classes.albumDetails}>
|
||||||
<Typography variant="h5" className={classes.albumTitle}>
|
<Typography variant="h5" className={classes.albumTitle}>
|
||||||
{record.name}
|
{record.name}
|
||||||
@@ -60,8 +224,23 @@ const AlbumDetails = ({ classes, record }) => {
|
|||||||
{' · '} <DurationField record={record} source={'duration'} /> {' · '}
|
{' · '} <DurationField record={record} source={'duration'} /> {' · '}
|
||||||
<SizeField record={record} source="size" />
|
<SizeField record={record} source="size" />
|
||||||
</Typography>
|
</Typography>
|
||||||
<StarButton record={record} resource={'album'} size={'large'} />
|
{isDesktop && record['comment'] && (
|
||||||
|
<AlbumComment
|
||||||
|
classes={classes}
|
||||||
|
record={record}
|
||||||
|
commentNumLines={commentNumLines}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
{!isDesktop && record['comment'] && (
|
||||||
|
<div>
|
||||||
|
<AlbumComment
|
||||||
|
classes={classes}
|
||||||
|
record={record}
|
||||||
|
commentNumLines={commentNumLines}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{isLightboxOpen && (
|
{isLightboxOpen && (
|
||||||
<Lightbox
|
<Lightbox
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ import {
|
|||||||
} from 'react-admin'
|
} from 'react-admin'
|
||||||
import { useMediaQuery } from '@material-ui/core'
|
import { useMediaQuery } from '@material-ui/core'
|
||||||
import StarBorderIcon from '@material-ui/icons/StarBorder'
|
import StarBorderIcon from '@material-ui/icons/StarBorder'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import {
|
import {
|
||||||
ArtistLinkField,
|
ArtistLinkField,
|
||||||
DurationField,
|
DurationField,
|
||||||
RangeField,
|
RangeField,
|
||||||
SimpleList,
|
SimpleList,
|
||||||
SizeField,
|
SizeField,
|
||||||
|
MultiLineTextField,
|
||||||
|
AlbumContextMenu,
|
||||||
} from '../common'
|
} from '../common'
|
||||||
import { AlbumContextMenu } from '../common'
|
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
|
||||||
import MultiLineTextField from '../common/MultiLineTextField'
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
columnIcon: {
|
columnIcon: {
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import React from 'react'
|
|||||||
import { useGetOne } from 'react-admin'
|
import { useGetOne } from 'react-admin'
|
||||||
import AlbumDetails from './AlbumDetails'
|
import AlbumDetails from './AlbumDetails'
|
||||||
import { Title } from '../common'
|
import { Title } from '../common'
|
||||||
import { useStyles } from './styles'
|
|
||||||
import { SongBulkActions } from '../common'
|
import { SongBulkActions } from '../common'
|
||||||
import AlbumActions from './AlbumActions'
|
import AlbumActions from './AlbumActions'
|
||||||
import AlbumSongs from './AlbumSongs'
|
import AlbumSongs from './AlbumSongs'
|
||||||
|
|
||||||
const AlbumShow = (props) => {
|
const AlbumShow = (props) => {
|
||||||
const classes = useStyles()
|
|
||||||
const { data: record, loading, error } = useGetOne('album', props.id)
|
const { data: record, loading, error } = useGetOne('album', props.id)
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -21,7 +19,7 @@ const AlbumShow = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AlbumDetails {...props} classes={classes} record={record} />
|
<AlbumDetails {...props} record={record} />
|
||||||
<AlbumSongs
|
<AlbumSongs
|
||||||
{...props}
|
{...props}
|
||||||
albumId={props.id}
|
albumId={props.id}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import { makeStyles } from '@material-ui/core/styles'
|
|
||||||
|
|
||||||
export const useStyles = makeStyles((theme) => ({
|
|
||||||
container: {
|
|
||||||
[theme.breakpoints.down('xs')]: {
|
|
||||||
padding: '0.7em',
|
|
||||||
minWidth: '24em',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.up('sm')]: {
|
|
||||||
padding: '1em',
|
|
||||||
minWidth: '32em',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
albumCover: {
|
|
||||||
display: 'inline-flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
cursor: 'pointer',
|
|
||||||
|
|
||||||
[theme.breakpoints.down('xs')]: {
|
|
||||||
height: '8em',
|
|
||||||
width: '8em',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.up('sm')]: {
|
|
||||||
height: '10em',
|
|
||||||
width: '10em',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.up('lg')]: {
|
|
||||||
height: '15em',
|
|
||||||
width: '15em',
|
|
||||||
},
|
|
||||||
'&:hover $playButton': {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
albumDetails: {
|
|
||||||
display: 'inline-block',
|
|
||||||
verticalAlign: 'top',
|
|
||||||
[theme.breakpoints.down('xs')]: {
|
|
||||||
width: '14em',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.up('sm')]: {
|
|
||||||
width: '26em',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.up('lg')]: {
|
|
||||||
width: '43em',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
albumTitle: {
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
@@ -4,10 +4,22 @@ import Typography from '@material-ui/core/Typography'
|
|||||||
import sanitizeFieldRestProps from './sanitizeFieldRestProps'
|
import sanitizeFieldRestProps from './sanitizeFieldRestProps'
|
||||||
import md5 from 'md5-hex'
|
import md5 from 'md5-hex'
|
||||||
|
|
||||||
const MultiLineTextField = memo(
|
export const MultiLineTextField = memo(
|
||||||
({ className, emptyText, source, record = {}, stripTags, ...rest }) => {
|
({
|
||||||
|
className,
|
||||||
|
emptyText,
|
||||||
|
source,
|
||||||
|
record,
|
||||||
|
firstLine,
|
||||||
|
maxLines,
|
||||||
|
addLabel,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
const value = get(record, source)
|
const value = get(record, source)
|
||||||
const lines = value ? value.split('\n') : []
|
let lines = value ? value.split('\n') : []
|
||||||
|
if (maxLines || firstLine) {
|
||||||
|
lines = lines.slice(firstLine, maxLines)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
@@ -18,20 +30,24 @@ const MultiLineTextField = memo(
|
|||||||
>
|
>
|
||||||
{lines.length === 0 && emptyText
|
{lines.length === 0 && emptyText
|
||||||
? emptyText
|
? emptyText
|
||||||
: lines.map((line, idx) => (
|
: lines.map((line, idx) =>
|
||||||
<div
|
line === '' ? (
|
||||||
data-testid={`${source}.${idx}`}
|
<br key={md5(line + idx)} />
|
||||||
key={md5(line)}
|
) : (
|
||||||
dangerouslySetInnerHTML={{ __html: line }}
|
<div
|
||||||
/>
|
data-testid={`${source}.${idx}`}
|
||||||
))}
|
key={md5(line + idx)}
|
||||||
|
dangerouslySetInnerHTML={{ __html: line }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
MultiLineTextField.defaultProps = {
|
MultiLineTextField.defaultProps = {
|
||||||
|
record: {},
|
||||||
addLabel: true,
|
addLabel: true,
|
||||||
|
firstLine: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MultiLineTextField
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import TableRow from '@material-ui/core/TableRow'
|
|||||||
import { BooleanField, DateField, TextField, useTranslate } from 'react-admin'
|
import { BooleanField, DateField, TextField, useTranslate } from 'react-admin'
|
||||||
import inflection from 'inflection'
|
import inflection from 'inflection'
|
||||||
import { BitrateField, SizeField } from './index'
|
import { BitrateField, SizeField } from './index'
|
||||||
import MultiLineTextField from './MultiLineTextField'
|
import { MultiLineTextField } from './MultiLineTextField'
|
||||||
|
|
||||||
export const SongDetails = (props) => {
|
export const SongDetails = (props) => {
|
||||||
const translate = useTranslate()
|
const translate = useTranslate()
|
||||||
|
|||||||
@@ -15,7 +15,16 @@ const useStyles = makeStyles({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const StarButton = ({ resource, record, color, visible, size }) => {
|
export const StarButton = ({
|
||||||
|
resource,
|
||||||
|
record,
|
||||||
|
color,
|
||||||
|
visible,
|
||||||
|
size,
|
||||||
|
component: Button,
|
||||||
|
addLabel,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const classes = useStyles({ color, visible, starred: record.starred })
|
const classes = useStyles({ color, visible, starred: record.starred })
|
||||||
const notify = useNotify()
|
const notify = useNotify()
|
||||||
@@ -56,18 +65,19 @@ export const StarButton = ({ resource, record, color, visible, size }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<Button
|
||||||
onClick={handleToggleStar}
|
onClick={handleToggleStar}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className={classes.star}
|
className={classes.star}
|
||||||
|
{...rest}
|
||||||
>
|
>
|
||||||
{record.starred ? (
|
{record.starred ? (
|
||||||
<StarIcon fontSize={size} />
|
<StarIcon fontSize={size} />
|
||||||
) : (
|
) : (
|
||||||
<StarBorderIcon fontSize={size} />
|
<StarBorderIcon fontSize={size} />
|
||||||
)}
|
)}
|
||||||
</IconButton>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +87,7 @@ StarButton.propTypes = {
|
|||||||
visible: PropTypes.bool,
|
visible: PropTypes.bool,
|
||||||
color: PropTypes.string,
|
color: PropTypes.string,
|
||||||
size: PropTypes.string,
|
size: PropTypes.string,
|
||||||
|
component: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
StarButton.defaultProps = {
|
StarButton.defaultProps = {
|
||||||
@@ -85,4 +96,5 @@ StarButton.defaultProps = {
|
|||||||
visible: true,
|
visible: true,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
|
component: IconButton,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export * from './ContextMenus'
|
|||||||
export * from './DocLink'
|
export * from './DocLink'
|
||||||
export * from './DurationField'
|
export * from './DurationField'
|
||||||
export * from './List'
|
export * from './List'
|
||||||
|
export * from './MultiLineTextField'
|
||||||
export * from './Pagination'
|
export * from './Pagination'
|
||||||
export * from './PlayButton'
|
export * from './PlayButton'
|
||||||
export * from './QuickFilter'
|
export * from './QuickFilter'
|
||||||
|
|||||||
Reference in New Issue
Block a user