import type { FieldPathList, FieldProps, RJSFSchema } from "@rjsf/utils"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; import { LuChevronDown, LuChevronRight, LuPlus, LuTrash2, } from "react-icons/lu"; import type { ConfigFormContext } from "@/types/configForm"; import get from "lodash/get"; import { isSubtreeModified } from "../utils"; type ReplaceRule = { pattern?: string; replacement?: string; }; function getItemSchema(schema: RJSFSchema): RJSFSchema | undefined { const items = schema.items; if (!items || typeof items !== "object" || Array.isArray(items)) { return undefined; } return items as RJSFSchema; } function getPropertyTitle(itemSchema: RJSFSchema | undefined, key: string) { const props = (itemSchema as { properties?: Record }) ?.properties; const title = props?.[key]?.title; return typeof title === "string" ? title : undefined; } export function ReplaceRulesField(props: FieldProps) { const { schema, formData, onChange, idSchema, disabled, readonly } = props; const formContext = props.registry?.formContext as | ConfigFormContext | undefined; const { t } = useTranslation(["common"]); const rules: ReplaceRule[] = useMemo(() => { if (!Array.isArray(formData)) { return []; } return formData as ReplaceRule[]; }, [formData]); const itemSchema = useMemo( () => getItemSchema(schema as RJSFSchema), [schema], ); const title = (schema as RJSFSchema).title; const description = (schema as RJSFSchema).description; const patternTitle = getPropertyTitle(itemSchema, "pattern"); const replacementTitle = getPropertyTitle(itemSchema, "replacement"); const hasItems = rules.length > 0; const emptyPath = useMemo(() => [] as FieldPathList, []); const fieldPath = (props as { fieldPathId?: { path?: FieldPathList } }).fieldPathId?.path ?? emptyPath; const isModified = useMemo(() => { const baselineRoot = formContext?.baselineFormData; const baselineValue = baselineRoot ? get(baselineRoot, fieldPath) : undefined; return isSubtreeModified( rules, baselineValue, formContext?.overrides, fieldPath, formContext?.formData, ); }, [fieldPath, formContext, rules]); const [open, setOpen] = useState(hasItems || isModified); useEffect(() => { if (isModified) { setOpen(true); } }, [isModified]); useEffect(() => { if (hasItems) { setOpen(true); } }, [hasItems]); const handleAdd = useCallback(() => { const next = [...rules, { pattern: "", replacement: "" }]; onChange(next, fieldPath); }, [fieldPath, onChange, rules]); const handleRemove = useCallback( (index: number) => { const next = rules.filter((_, i) => i !== index); onChange(next, fieldPath); }, [fieldPath, onChange, rules], ); const handleUpdate = useCallback( (index: number, patch: Partial) => { const next = rules.map((rule, i) => { if (i !== index) { return rule; } return { ...rule, ...patch, }; }); onChange(next, fieldPath); }, [fieldPath, onChange, rules], ); const baseId = idSchema?.$id || "replace_rules"; const deleteLabel = t("button.delete", { ns: "common", defaultValue: "Delete", }); return (
{title} {description && (

{description}

)}
{open ? ( ) : ( )}
{rules.length > 0 && (
{patternTitle && ( )}
{replacementTitle && ( )}
)}
{rules.map((rule, index) => { const patternId = `${baseId}-${index}-pattern`; const replacementId = `${baseId}-${index}-replacement`; return (
handleUpdate(index, { pattern: e.target.value }) } />
handleUpdate(index, { replacement: e.target.value }) } />
); })}
); } export default ReplaceRulesField;