mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-12 20:37:12 +02:00
* fix(ui): use stock array renderer for plugins config form Signed-off-by: Deluan <deluan@navidrome.org> * fix(plugins): enforce minimum user tokens and require users field Signed-off-by: Deluan <deluan@navidrome.org> * fix(ui): simplify error handling in control state hook Signed-off-by: Deluan <deluan@navidrome.org> * fix(ui): remove "None" MenuItem from OutlinedEnumControl Signed-off-by: Deluan <deluan@navidrome.org> * fix(ui): enhance error handling by returning field info and path in validation errors Signed-off-by: Deluan <deluan@navidrome.org> * fix(ui): update OutlinedEnumControl to handle empty values and remove "None" option when required Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
123 lines
3.6 KiB
JavaScript
123 lines
3.6 KiB
JavaScript
import React, { useCallback, useState, useMemo } from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { Card, CardContent, Typography, Box } from '@material-ui/core'
|
|
import Alert from '@material-ui/lab/Alert'
|
|
import { SchemaConfigEditor } from './SchemaConfigEditor'
|
|
|
|
// Format error with field title and full path for nested fields
|
|
const formatError = (error, schema) => {
|
|
// Get path parts from various error formats
|
|
const rawPath =
|
|
error.dataPath || error.property || error.instancePath?.replace(/\//g, '.')
|
|
const parts = rawPath?.split('.').filter(Boolean) || []
|
|
|
|
// Navigate schema to find field title, build bracket-notation path
|
|
let currentSchema = schema
|
|
let fieldName = parts[parts.length - 1]
|
|
const pathParts = []
|
|
|
|
for (const part of parts) {
|
|
if (/^\d+$/.test(part)) {
|
|
pathParts.push(`[${part}]`)
|
|
currentSchema = currentSchema?.items
|
|
} else {
|
|
fieldName = currentSchema?.properties?.[part]?.title || part
|
|
pathParts.push(part)
|
|
currentSchema = currentSchema?.properties?.[part]
|
|
}
|
|
}
|
|
|
|
const path = pathParts.join('.').replace(/\.\[/g, '[')
|
|
const isNested = path.includes('[') || path.includes('.')
|
|
// Replace property name in message with full path for nested fields
|
|
const message = isNested
|
|
? error.message.replace(/'[^']+'\s*$/, `'${path}'`)
|
|
: error.message
|
|
|
|
return { fieldName, message }
|
|
}
|
|
|
|
export const ConfigCard = ({
|
|
manifest,
|
|
configData,
|
|
onConfigDataChange,
|
|
classes,
|
|
translate,
|
|
}) => {
|
|
const [validationErrors, setValidationErrors] = useState([])
|
|
|
|
// Handle changes from JSONForms
|
|
const handleChange = useCallback(
|
|
(newData, errors) => {
|
|
setValidationErrors(errors || [])
|
|
onConfigDataChange(newData, errors)
|
|
},
|
|
[onConfigDataChange],
|
|
)
|
|
|
|
// Only show config card if manifest has config schema defined
|
|
const hasConfigSchema = manifest?.config?.schema
|
|
|
|
// Format validation errors with proper field names
|
|
const formattedErrors = useMemo(() => {
|
|
if (!hasConfigSchema) return []
|
|
return validationErrors.map((error) =>
|
|
formatError(error, manifest.config.schema),
|
|
)
|
|
}, [validationErrors, manifest, hasConfigSchema])
|
|
|
|
if (!hasConfigSchema) {
|
|
return null
|
|
}
|
|
|
|
const { schema, uiSchema } = manifest.config
|
|
|
|
return (
|
|
<Card className={classes.section}>
|
|
<CardContent>
|
|
<Typography variant="h6" className={classes.sectionTitle}>
|
|
{translate('resources.plugin.sections.configuration')}
|
|
</Typography>
|
|
|
|
{formattedErrors.length > 0 && (
|
|
<Box mb={2}>
|
|
<Alert severity="error">
|
|
{translate('resources.plugin.messages.configValidationError')}
|
|
<ul style={{ margin: '8px 0 0', paddingLeft: 20 }}>
|
|
{formattedErrors.map((error, index) => (
|
|
<li key={index}>
|
|
{error.fieldName && <strong>{error.fieldName}</strong>}
|
|
{error.fieldName && ': '}
|
|
{error.message}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</Alert>
|
|
</Box>
|
|
)}
|
|
|
|
<Box mt={formattedErrors.length > 0 ? 0 : 2}>
|
|
<SchemaConfigEditor
|
|
schema={schema}
|
|
uiSchema={uiSchema}
|
|
data={configData}
|
|
onChange={handleChange}
|
|
/>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
ConfigCard.propTypes = {
|
|
manifest: PropTypes.shape({
|
|
config: PropTypes.shape({
|
|
schema: PropTypes.object,
|
|
uiSchema: PropTypes.object,
|
|
}),
|
|
}),
|
|
configData: PropTypes.object,
|
|
onConfigDataChange: PropTypes.func.isRequired,
|
|
classes: PropTypes.object.isRequired,
|
|
translate: PropTypes.func.isRequired,
|
|
}
|