2026-01-23 17:23:52 +03:00
|
|
|
// ConfigForm - Main RJSF form wrapper component
|
|
|
|
|
import Form from "@rjsf/core";
|
|
|
|
|
import validator from "@rjsf/validator-ajv8";
|
|
|
|
|
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
|
|
|
|
|
import type { IChangeEvent } from "@rjsf/core";
|
|
|
|
|
import { frigateTheme } from "./theme";
|
|
|
|
|
import { transformSchema } from "@/lib/config-schema";
|
|
|
|
|
import { createErrorTransformer } from "@/lib/config-schema/errorMessages";
|
2026-01-26 19:50:54 +03:00
|
|
|
import { useMemo, useCallback } from "react";
|
2026-01-23 17:23:52 +03:00
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
|
|
|
|
|
export interface ConfigFormProps {
|
|
|
|
|
/** JSON Schema for the form */
|
|
|
|
|
schema: RJSFSchema;
|
|
|
|
|
/** Current form data */
|
2026-01-25 19:33:57 +03:00
|
|
|
formData?: unknown;
|
2026-01-23 17:23:52 +03:00
|
|
|
/** Called when form data changes */
|
2026-01-25 19:33:57 +03:00
|
|
|
onChange?: (data: unknown) => void;
|
2026-01-23 17:23:52 +03:00
|
|
|
/** Called when form is submitted */
|
2026-01-25 19:33:57 +03:00
|
|
|
onSubmit?: (data: unknown) => void;
|
2026-01-23 17:23:52 +03:00
|
|
|
/** Called when form has errors on submit */
|
|
|
|
|
onError?: (errors: unknown[]) => void;
|
|
|
|
|
/** Additional uiSchema overrides */
|
|
|
|
|
uiSchema?: UiSchema;
|
|
|
|
|
/** Field ordering */
|
|
|
|
|
fieldOrder?: string[];
|
2026-01-26 02:29:52 +03:00
|
|
|
/** Field groups for layout */
|
|
|
|
|
fieldGroups?: Record<string, string[]>;
|
2026-01-23 17:23:52 +03:00
|
|
|
/** Fields to hide */
|
|
|
|
|
hiddenFields?: string[];
|
|
|
|
|
/** Fields marked as advanced (collapsed by default) */
|
|
|
|
|
advancedFields?: string[];
|
|
|
|
|
/** Whether form is disabled */
|
|
|
|
|
disabled?: boolean;
|
|
|
|
|
/** Whether form is read-only */
|
|
|
|
|
readonly?: boolean;
|
|
|
|
|
/** Whether to show submit button */
|
|
|
|
|
showSubmit?: boolean;
|
|
|
|
|
/** Custom class name */
|
|
|
|
|
className?: string;
|
|
|
|
|
/** Live validation mode */
|
|
|
|
|
liveValidate?: boolean;
|
|
|
|
|
/** Form context passed to all widgets */
|
|
|
|
|
formContext?: Record<string, unknown>;
|
2026-01-23 19:04:50 +03:00
|
|
|
/** i18n namespace for field labels */
|
|
|
|
|
i18nNamespace?: string;
|
2026-01-23 17:23:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ConfigForm({
|
|
|
|
|
schema,
|
|
|
|
|
formData,
|
|
|
|
|
onChange,
|
|
|
|
|
onSubmit,
|
|
|
|
|
onError,
|
|
|
|
|
uiSchema: customUiSchema,
|
|
|
|
|
fieldOrder,
|
2026-01-26 02:29:52 +03:00
|
|
|
fieldGroups,
|
2026-01-23 17:23:52 +03:00
|
|
|
hiddenFields,
|
|
|
|
|
advancedFields,
|
|
|
|
|
disabled = false,
|
|
|
|
|
readonly = false,
|
|
|
|
|
showSubmit = true,
|
|
|
|
|
className,
|
2026-01-26 19:50:54 +03:00
|
|
|
liveValidate = true,
|
2026-01-23 17:23:52 +03:00
|
|
|
formContext,
|
2026-01-23 19:04:50 +03:00
|
|
|
i18nNamespace,
|
2026-01-23 17:23:52 +03:00
|
|
|
}: ConfigFormProps) {
|
2026-01-26 03:47:31 +03:00
|
|
|
const { t, i18n } = useTranslation([
|
2026-01-26 03:20:30 +03:00
|
|
|
i18nNamespace || "common",
|
|
|
|
|
"views/settings",
|
2026-01-26 03:47:31 +03:00
|
|
|
"config/validation",
|
2026-01-26 03:20:30 +03:00
|
|
|
]);
|
2026-01-23 17:23:52 +03:00
|
|
|
|
|
|
|
|
// Determine which fields to hide based on advanced toggle
|
|
|
|
|
const effectiveHiddenFields = useMemo(() => {
|
2026-01-26 19:50:54 +03:00
|
|
|
return hiddenFields;
|
|
|
|
|
}, [hiddenFields]);
|
2026-01-23 17:23:52 +03:00
|
|
|
|
|
|
|
|
// Transform schema and generate uiSchema
|
|
|
|
|
const { schema: transformedSchema, uiSchema: generatedUiSchema } = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
transformSchema(schema, {
|
|
|
|
|
fieldOrder,
|
|
|
|
|
hiddenFields: effectiveHiddenFields,
|
2026-01-26 19:50:54 +03:00
|
|
|
advancedFields: advancedFields,
|
2026-01-23 19:04:50 +03:00
|
|
|
i18nNamespace,
|
2026-01-23 17:23:52 +03:00
|
|
|
}),
|
2026-01-26 19:50:54 +03:00
|
|
|
[schema, fieldOrder, effectiveHiddenFields, advancedFields, i18nNamespace],
|
2026-01-23 17:23:52 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Merge generated uiSchema with custom overrides
|
|
|
|
|
const finalUiSchema = useMemo(
|
|
|
|
|
() => ({
|
|
|
|
|
...generatedUiSchema,
|
2026-01-26 02:29:52 +03:00
|
|
|
"ui:groups": fieldGroups,
|
2026-01-23 17:23:52 +03:00
|
|
|
...customUiSchema,
|
|
|
|
|
"ui:submitButtonOptions": showSubmit
|
|
|
|
|
? { norender: false }
|
|
|
|
|
: { norender: true },
|
|
|
|
|
}),
|
2026-01-26 02:29:52 +03:00
|
|
|
[generatedUiSchema, customUiSchema, showSubmit, fieldGroups],
|
2026-01-23 17:23:52 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Create error transformer for user-friendly error messages
|
2026-01-26 03:47:31 +03:00
|
|
|
const errorTransformer = useMemo(() => createErrorTransformer(i18n), [i18n]);
|
2026-01-23 17:23:52 +03:00
|
|
|
|
|
|
|
|
const handleChange = useCallback(
|
|
|
|
|
(e: IChangeEvent) => {
|
|
|
|
|
onChange?.(e.formData);
|
|
|
|
|
},
|
|
|
|
|
[onChange],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleSubmit = useCallback(
|
|
|
|
|
(e: IChangeEvent) => {
|
|
|
|
|
onSubmit?.(e.formData);
|
|
|
|
|
},
|
|
|
|
|
[onSubmit],
|
|
|
|
|
);
|
|
|
|
|
|
2026-01-23 19:04:50 +03:00
|
|
|
// Extended form context with i18n info
|
|
|
|
|
const extendedFormContext = useMemo(
|
|
|
|
|
() => ({
|
|
|
|
|
...formContext,
|
|
|
|
|
i18nNamespace,
|
|
|
|
|
t,
|
|
|
|
|
}),
|
|
|
|
|
[formContext, i18nNamespace, t],
|
|
|
|
|
);
|
|
|
|
|
|
2026-01-23 17:23:52 +03:00
|
|
|
return (
|
|
|
|
|
<div className={cn("config-form", className)}>
|
|
|
|
|
<Form
|
|
|
|
|
schema={transformedSchema}
|
|
|
|
|
uiSchema={finalUiSchema}
|
|
|
|
|
formData={formData}
|
|
|
|
|
validator={validator}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
onSubmit={handleSubmit}
|
|
|
|
|
onError={onError}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
readonly={readonly}
|
|
|
|
|
liveValidate={liveValidate}
|
2026-01-23 19:04:50 +03:00
|
|
|
formContext={extendedFormContext}
|
2026-01-23 17:23:52 +03:00
|
|
|
transformErrors={errorTransformer}
|
|
|
|
|
{...frigateTheme}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default ConfigForm;
|