mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
The config flag gates all image uploads (artists, radios, playlists), not just cover art. Rename it to accurately reflect its scope across the backend config, native API permission check, Subsonic CoverArtRole, serve_index JSON key, and frontend config.
140 lines
3.5 KiB
JavaScript
140 lines
3.5 KiB
JavaScript
import { IconButton, Tooltip } from '@material-ui/core'
|
|
import { makeStyles } from '@material-ui/core/styles'
|
|
import PhotoCameraIcon from '@material-ui/icons/PhotoCamera'
|
|
import DeleteIcon from '@material-ui/icons/Delete'
|
|
import { useTranslate, useNotify, useRefresh } from 'react-admin'
|
|
import { useCallback, useRef } from 'react'
|
|
import config from '../config'
|
|
import { REST_URL } from '../consts'
|
|
import { httpClient } from '../dataProvider'
|
|
|
|
const useStyles = makeStyles(() => ({
|
|
coverOverlay: {
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
right: 0,
|
|
display: 'flex',
|
|
gap: '2px',
|
|
padding: '2px',
|
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
borderRadius: '4px 0 0 0',
|
|
opacity: 0,
|
|
transition: 'opacity 0.2s ease-in-out',
|
|
'*:hover > &': {
|
|
opacity: 1,
|
|
},
|
|
},
|
|
overlayButton: {
|
|
color: '#fff',
|
|
padding: '4px',
|
|
'&:hover': {
|
|
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
},
|
|
},
|
|
overlayIcon: {
|
|
fontSize: '1.2rem',
|
|
},
|
|
}))
|
|
|
|
export const ImageUploadOverlay = ({
|
|
entityType,
|
|
entityId,
|
|
hasUploadedImage,
|
|
onImageChange,
|
|
}) => {
|
|
const translate = useTranslate()
|
|
const notify = useNotify()
|
|
const refresh = useRefresh()
|
|
const classes = useStyles()
|
|
const fileInputRef = useRef(null)
|
|
|
|
const canEdit =
|
|
config.enableArtworkUpload || localStorage.getItem('role') === 'admin'
|
|
|
|
const handleUploadClick = useCallback((e) => {
|
|
e.stopPropagation()
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.click()
|
|
}
|
|
}, [])
|
|
|
|
const handleFileChange = useCallback(
|
|
async (e) => {
|
|
const file = e.target.files[0]
|
|
if (!file || !entityId) return
|
|
|
|
const formData = new FormData()
|
|
formData.append('image', file)
|
|
|
|
try {
|
|
await httpClient(`${REST_URL}/${entityType}/${entityId}/image`, {
|
|
method: 'POST',
|
|
headers: new Headers({}),
|
|
body: formData,
|
|
})
|
|
notify(`message.coverUploaded`, 'success')
|
|
if (onImageChange) onImageChange()
|
|
refresh()
|
|
} catch (err) {
|
|
notify(`message.coverUploadError`, 'warning')
|
|
}
|
|
|
|
e.target.value = ''
|
|
},
|
|
[entityType, entityId, notify, refresh, onImageChange],
|
|
)
|
|
|
|
const handleRemoveCover = useCallback(
|
|
async (e) => {
|
|
e.stopPropagation()
|
|
if (!entityId) return
|
|
|
|
try {
|
|
await httpClient(`${REST_URL}/${entityType}/${entityId}/image`, {
|
|
method: 'DELETE',
|
|
})
|
|
notify(`message.coverRemoved`, 'success')
|
|
if (onImageChange) onImageChange()
|
|
refresh()
|
|
} catch (err) {
|
|
notify(`message.coverRemoveError`, 'warning')
|
|
}
|
|
},
|
|
[entityType, entityId, notify, refresh, onImageChange],
|
|
)
|
|
|
|
if (!canEdit) return null
|
|
|
|
return (
|
|
<div className={classes.coverOverlay}>
|
|
<Tooltip title={translate(`message.uploadCover`)}>
|
|
<IconButton
|
|
className={classes.overlayButton}
|
|
onClick={handleUploadClick}
|
|
size="small"
|
|
>
|
|
<PhotoCameraIcon className={classes.overlayIcon} />
|
|
</IconButton>
|
|
</Tooltip>
|
|
{hasUploadedImage && (
|
|
<Tooltip title={translate(`message.removeCover`)}>
|
|
<IconButton
|
|
className={classes.overlayButton}
|
|
onClick={handleRemoveCover}
|
|
size="small"
|
|
>
|
|
<DeleteIcon className={classes.overlayIcon} />
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
style={{ display: 'none' }}
|
|
onChange={handleFileChange}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|