mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-31 15:37:51 +01:00
feat(library): enhance LibraryCreate and LibraryEdit components with additional props and styling
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
f1cea72715
commit
20968ef0b9
6 changed files with 86 additions and 27 deletions
|
|
@ -1,4 +1,7 @@
|
|||
import React from 'react'
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
import { makeStyles, alpha } from '@material-ui/core/styles'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
useNotify,
|
||||
useDeleteWithConfirmController,
|
||||
|
|
@ -7,9 +10,30 @@ import {
|
|||
useTranslate,
|
||||
useRedirect,
|
||||
} from 'react-admin'
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
|
||||
const DeleteLibraryButton = ({ record, resource, basePath, ...props }) => {
|
||||
const useStyles = makeStyles(
|
||||
(theme) => ({
|
||||
deleteButton: {
|
||||
color: theme.palette.error.main,
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.palette.error.main, 0.12),
|
||||
// Reset on mouse devices
|
||||
'@media (hover: none)': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ name: 'RaDeleteWithConfirmButton' },
|
||||
)
|
||||
|
||||
const DeleteLibraryButton = ({
|
||||
record,
|
||||
resource,
|
||||
basePath,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const translate = useTranslate()
|
||||
const notify = useNotify()
|
||||
const redirect = useRedirect()
|
||||
|
|
@ -29,12 +53,14 @@ const DeleteLibraryButton = ({ record, resource, basePath, ...props }) => {
|
|||
onSuccess,
|
||||
})
|
||||
|
||||
const classes = useStyles(props)
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
label="ra.action.delete"
|
||||
onClick={handleDialogOpen}
|
||||
disabled={loading}
|
||||
className={clsx('ra-delete-button', classes.deleteButton, className)}
|
||||
{...props}
|
||||
>
|
||||
<DeleteIcon />
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const LibraryCreate = (props) => {
|
|||
<Create title={<Title subTitle={title} />} {...props}>
|
||||
<SimpleForm save={save} variant={'outlined'}>
|
||||
<TextInput source="name" validate={[required()]} />
|
||||
<TextInput source="path" validate={[required()]} />
|
||||
<TextInput source="path" validate={[required()]} fullWidth />
|
||||
</SimpleForm>
|
||||
</Create>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,30 +11,39 @@ import {
|
|||
useNotify,
|
||||
useRedirect,
|
||||
NumberInput,
|
||||
Toolbar,
|
||||
} from 'react-admin'
|
||||
import { Typography, Box, Toolbar } from '@material-ui/core'
|
||||
import { Typography, Box } from '@material-ui/core'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import DeleteLibraryButton from './DeleteLibraryButton'
|
||||
import { Title } from '../common'
|
||||
import { formatBytes, formatDuration2, formatNumber } from '../utils/index.js'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
toolbar: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
})
|
||||
|
||||
const LibraryTitle = ({ record }) => {
|
||||
const translate = useTranslate()
|
||||
const resourceName = translate('resources.library.name', { smart_count: 1 })
|
||||
return (
|
||||
<Title subTitle={`${resourceName} ${record ? `\"${record.name}\"` : ''}`} />
|
||||
<Title subTitle={`${resourceName} ${record ? `"${record.name}"` : ''}`} />
|
||||
)
|
||||
}
|
||||
|
||||
const CustomToolbar = (props) => (
|
||||
<Toolbar {...props}>
|
||||
<Box display="flex" justifyContent="space-between" width="100%">
|
||||
<SaveButton
|
||||
handleSubmitWithRedirect={props.handleSubmitWithRedirect}
|
||||
saving={props.saving}
|
||||
pristine={props.pristine}
|
||||
const CustomToolbar = ({ showDelete, ...props }) => (
|
||||
<Toolbar {...props} classes={useStyles()}>
|
||||
<SaveButton disabled={props.pristine} />
|
||||
{showDelete && (
|
||||
<DeleteLibraryButton
|
||||
record={props.record}
|
||||
resource="library"
|
||||
basePath="/library"
|
||||
/>
|
||||
<DeleteLibraryButton />
|
||||
</Box>
|
||||
)}
|
||||
</Toolbar>
|
||||
)
|
||||
|
||||
|
|
@ -44,6 +53,9 @@ const LibraryEdit = (props) => {
|
|||
const notify = useNotify()
|
||||
const redirect = useRedirect()
|
||||
|
||||
// Library ID 1 is protected (main library)
|
||||
const canDelete = props.id !== '1'
|
||||
|
||||
const save = useCallback(
|
||||
async (values) => {
|
||||
try {
|
||||
|
|
@ -83,8 +95,19 @@ const LibraryEdit = (props) => {
|
|||
{translate('resources.library.sections.basic')}
|
||||
</Typography>
|
||||
|
||||
<TextInput source="name" validate={[required()]} />
|
||||
<TextInput source="path" validate={[required()]} fullWidth />
|
||||
<TextInput
|
||||
source="name"
|
||||
validate={[required()]}
|
||||
variant="outlined"
|
||||
InputProps={{ readOnly: !canDelete }} // Disable editing path for library 1
|
||||
/>
|
||||
<TextInput
|
||||
source="path"
|
||||
validate={[required()]}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
InputProps={{ readOnly: !canDelete }} // Disable editing path for library 1
|
||||
/>
|
||||
|
||||
<Box mt="2em" />
|
||||
|
||||
|
|
@ -96,18 +119,20 @@ const LibraryEdit = (props) => {
|
|||
<Box display="flex">
|
||||
<Box flex={1} mr="0.5em">
|
||||
<NumberInput
|
||||
disabled
|
||||
InputProps={{ readOnly: true }}
|
||||
resource={'library'}
|
||||
source={'totalSongs'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={1} ml="0.5em">
|
||||
<NumberInput
|
||||
disabled
|
||||
InputProps={{ readOnly: true }}
|
||||
resource={'library'}
|
||||
source={'totalAlbums'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -115,19 +140,21 @@ const LibraryEdit = (props) => {
|
|||
<Box display="flex">
|
||||
<Box flex={1} mr="0.5em">
|
||||
<NumberInput
|
||||
disabled
|
||||
InputProps={{ readOnly: true }}
|
||||
resource={'library'}
|
||||
source={'totalArtists'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={1} ml="0.5em">
|
||||
<TextInput
|
||||
disabled
|
||||
InputProps={{ readOnly: true }}
|
||||
resource={'library'}
|
||||
source={'totalSize'}
|
||||
format={formatBytes}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -135,19 +162,21 @@ const LibraryEdit = (props) => {
|
|||
<Box display="flex">
|
||||
<Box flex={1} mr="0.5em">
|
||||
<TextInput
|
||||
disabled
|
||||
InputProps={{ readOnly: true }}
|
||||
resource={'library'}
|
||||
source={'totalDuration'}
|
||||
format={formatDuration2}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={1} ml="0.5em">
|
||||
<TextInput
|
||||
disabled
|
||||
InputProps={{ readOnly: true }}
|
||||
resource={'library'}
|
||||
source={'totalMissingFiles'}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -208,6 +237,8 @@ const LibraryEdit = (props) => {
|
|||
handleSubmitWithRedirect={formProps.handleSubmitWithRedirect}
|
||||
pristine={formProps.pristine}
|
||||
saving={formProps.saving}
|
||||
record={formProps.record}
|
||||
showDelete={canDelete}
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { MdLibraryMusic } from 'react-icons/md'
|
||||
import LibraryList from './LibraryList'
|
||||
import LibraryEdit from './LibraryEdit'
|
||||
import LibraryCreate from './LibraryCreate'
|
||||
|
||||
export default {
|
||||
icon: MdLibraryMusic,
|
||||
list: LibraryList,
|
||||
edit: LibraryEdit,
|
||||
create: LibraryCreate,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const formatDuration2 = (totalSeconds) => {
|
|||
const seconds = Math.floor(totalSeconds % 60)
|
||||
|
||||
const parts = []
|
||||
|
||||
|
||||
if (days > 0) {
|
||||
// When days are present, show only d h m (3 levels max)
|
||||
parts.push(`${days}d`)
|
||||
|
|
@ -57,7 +57,7 @@ export const formatDuration2 = (totalSeconds) => {
|
|||
parts.push(`${seconds}s`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,12 +110,12 @@ describe('formatDuration2', () => {
|
|||
|
||||
it('formats days with maximum 3 levels (d h m)', () => {
|
||||
expect(formatDuration2(86400)).toEqual('1d')
|
||||
expect(formatDuration2(86461)).toEqual('1d 1m') // seconds dropped when days present
|
||||
expect(formatDuration2(90061)).toEqual('1d 1h 1m') // seconds dropped when days present
|
||||
expect(formatDuration2(86461)).toEqual('1d 1m') // seconds dropped when days present
|
||||
expect(formatDuration2(90061)).toEqual('1d 1h 1m') // seconds dropped when days present
|
||||
expect(formatDuration2(172800)).toEqual('2d')
|
||||
expect(formatDuration2(176400)).toEqual('2d 1h')
|
||||
expect(formatDuration2(176460)).toEqual('2d 1h 1m')
|
||||
expect(formatDuration2(176461)).toEqual('2d 1h 1m') // seconds dropped when days present
|
||||
expect(formatDuration2(176461)).toEqual('2d 1h 1m') // seconds dropped when days present
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue