diff --git a/ui/src/album/AlbumDetails.js b/ui/src/album/AlbumDetails.js index 0827c1a0d..166d2af64 100644 --- a/ui/src/album/AlbumDetails.js +++ b/ui/src/album/AlbumDetails.js @@ -1,15 +1,170 @@ -import React from 'react' -import { Card, CardContent, CardMedia, Typography } from '@material-ui/core' +import React, { useMemo } from 'react' +import { + Card, + CardContent, + CardMedia, + Typography, + Collapse, + makeStyles, + IconButton, + Fab, + useMediaQuery, +} from '@material-ui/core' +import classnames from 'classnames' import { useTranslate } from 'react-admin' import Lightbox from 'react-image-lightbox' import 'react-image-lightbox/style.css' +import ExpandMoreIcon from '@material-ui/icons/ExpandMore' import subsonic from '../subsonic' -import { DurationField, StarButton, SizeField } from '../common' -import { ArtistLinkField, formatRange } from '../common' +import { + 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 ( +
+
+ +
+ {commentNumLines > 1 && ( + + + + )} + + + +
+ ) +} + +const AlbumDetails = ({ record }) => { + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('lg')) + const classes = useStyles() const [isLightboxOpen, setLightboxOpen] = React.useState(false) const translate = useTranslate() + + const commentNumLines = useMemo( + () => record.comment && record.comment.split('\n').length, + [record] + ) + const genreYear = (record) => { let genreDateLine = [] if (record.genre) { @@ -38,14 +193,23 @@ const AlbumDetails = ({ classes, record }) => { () => setLightboxOpen(false), [] ) - return ( + > + + {record.name} @@ -60,8 +224,23 @@ const AlbumDetails = ({ classes, record }) => { {' · '} {' · '} - + {isDesktop && record['comment'] && ( + + )} + {!isDesktop && record['comment'] && ( +
+ +
+ )} {isLightboxOpen && ( { - const classes = useStyles() const { data: record, loading, error } = useGetOne('album', props.id) if (loading) { @@ -21,7 +19,7 @@ const AlbumShow = (props) => { return ( <> - + ({ - 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', - }, -})) diff --git a/ui/src/common/MultiLineTextField.js b/ui/src/common/MultiLineTextField.js index 2f267925f..bc36326a3 100644 --- a/ui/src/common/MultiLineTextField.js +++ b/ui/src/common/MultiLineTextField.js @@ -4,10 +4,22 @@ import Typography from '@material-ui/core/Typography' import sanitizeFieldRestProps from './sanitizeFieldRestProps' import md5 from 'md5-hex' -const MultiLineTextField = memo( - ({ className, emptyText, source, record = {}, stripTags, ...rest }) => { +export const MultiLineTextField = memo( + ({ + className, + emptyText, + source, + record, + firstLine, + maxLines, + addLabel, + ...rest + }) => { 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 ( {lines.length === 0 && emptyText ? emptyText - : lines.map((line, idx) => ( -
- ))} + : lines.map((line, idx) => + line === '' ? ( +
+ ) : ( +
+ ) + )} ) } ) MultiLineTextField.defaultProps = { + record: {}, addLabel: true, + firstLine: 0, } - -export default MultiLineTextField diff --git a/ui/src/common/SongDetails.js b/ui/src/common/SongDetails.js index 714c7e0b7..b26ceed22 100644 --- a/ui/src/common/SongDetails.js +++ b/ui/src/common/SongDetails.js @@ -8,7 +8,7 @@ import TableRow from '@material-ui/core/TableRow' import { BooleanField, DateField, TextField, useTranslate } from 'react-admin' import inflection from 'inflection' import { BitrateField, SizeField } from './index' -import MultiLineTextField from './MultiLineTextField' +import { MultiLineTextField } from './MultiLineTextField' export const SongDetails = (props) => { const translate = useTranslate() diff --git a/ui/src/common/StarButton.js b/ui/src/common/StarButton.js index 9958747fa..133f35d15 100644 --- a/ui/src/common/StarButton.js +++ b/ui/src/common/StarButton.js @@ -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 classes = useStyles({ color, visible, starred: record.starred }) const notify = useNotify() @@ -56,18 +65,19 @@ export const StarButton = ({ resource, record, color, visible, size }) => { } return ( - {record.starred ? ( ) : ( )} - + ) } @@ -77,6 +87,7 @@ StarButton.propTypes = { visible: PropTypes.bool, color: PropTypes.string, size: PropTypes.string, + component: PropTypes.object, } StarButton.defaultProps = { @@ -85,4 +96,5 @@ StarButton.defaultProps = { visible: true, size: 'small', color: 'inherit', + component: IconButton, } diff --git a/ui/src/common/index.js b/ui/src/common/index.js index 856f5d64c..2cae83232 100644 --- a/ui/src/common/index.js +++ b/ui/src/common/index.js @@ -6,6 +6,7 @@ export * from './ContextMenus' export * from './DocLink' export * from './DurationField' export * from './List' +export * from './MultiLineTextField' export * from './Pagination' export * from './PlayButton' export * from './QuickFilter'