mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-30 17:11:14 +03:00
* tweak language * show validation errors in json response * fix export hwaccel args field in UI * increase annotation offset consts * fix save button race conditions, add reset spinner, and fix enrichments profile leak - Disable both Save and SaveAll buttons while either operation is in progress so users cannot trigger concurrent saves - Show activity indicator on Reset to Default/Global button during the API call - Enrichments panes (semantic search, genai, face recognition) now always show base config fields regardless of profile selection in the header dropdown * fix genai additional_concerns validation error with textarea array widget The additional_concerns field is list[str] in the backend but was using the textarea widget which produces a string value, causing validation errors. Created a TextareaArrayWidget that converts between array (one item per line) and textarea display, and switched additional_concerns to use it * populate and sort global audio filters for all audio labels * add column labels in profiles view * enforce a minimum value of 2 for min_initialized * reuse widget and refactor for multiline * fix * change record copy preset to transcode audio to aac
105 lines
2.7 KiB
TypeScript
105 lines
2.7 KiB
TypeScript
// Widget that displays an array as editable text.
|
|
// Single-line mode (default): space-separated in an Input.
|
|
// Multiline mode (options.multiline): one item per line in a Textarea.
|
|
import type { WidgetProps } from "@rjsf/utils";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { cn } from "@/lib/utils";
|
|
import { getSizedFieldClassName } from "../utils";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
function arrayToText(value: unknown, multiline: boolean): string {
|
|
const sep = multiline ? "\n" : " ";
|
|
if (Array.isArray(value) && value.length > 0) {
|
|
return value.join(sep);
|
|
}
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function textToArray(text: string, multiline: boolean): string[] {
|
|
if (text.trim() === "") {
|
|
return [];
|
|
}
|
|
return multiline
|
|
? text.split("\n").filter((line) => line.trim() !== "")
|
|
: text.trim().split(/\s+/);
|
|
}
|
|
|
|
export function ArrayAsTextWidget(props: WidgetProps) {
|
|
const {
|
|
id,
|
|
value,
|
|
disabled,
|
|
readonly,
|
|
onChange,
|
|
onBlur,
|
|
onFocus,
|
|
placeholder,
|
|
schema,
|
|
options,
|
|
} = props;
|
|
|
|
const multiline = !!(options.multiline as boolean);
|
|
|
|
// Local state keeps raw text so newlines aren't stripped mid-typing
|
|
const [text, setText] = useState(() => arrayToText(value, multiline));
|
|
|
|
useEffect(() => {
|
|
setText(arrayToText(value, multiline));
|
|
}, [value, multiline]);
|
|
|
|
const fieldClassName = multiline
|
|
? getSizedFieldClassName(options, "md")
|
|
: undefined;
|
|
|
|
const handleInputChange = useCallback(
|
|
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
const raw = e.target.value;
|
|
setText(raw);
|
|
onChange(textToArray(raw, multiline));
|
|
},
|
|
[onChange, multiline],
|
|
);
|
|
|
|
const handleBlur = useCallback(
|
|
(e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
// Clean up: strip empty entries and sync
|
|
const cleaned = textToArray(e.target.value, multiline);
|
|
onChange(cleaned);
|
|
setText(arrayToText(cleaned, multiline));
|
|
onBlur?.(id, e.target.value);
|
|
},
|
|
[id, onChange, onBlur, multiline],
|
|
);
|
|
|
|
if (multiline) {
|
|
return (
|
|
<Textarea
|
|
id={id}
|
|
className={cn("text-md", fieldClassName)}
|
|
value={text}
|
|
disabled={disabled || readonly}
|
|
rows={(options.rows as number) || 3}
|
|
onChange={handleInputChange}
|
|
onBlur={handleBlur}
|
|
onFocus={(e) => onFocus?.(id, e.target.value)}
|
|
aria-label={schema.title}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Input
|
|
value={text}
|
|
onChange={handleInputChange}
|
|
onBlur={handleBlur}
|
|
disabled={disabled}
|
|
readOnly={readonly}
|
|
placeholder={placeholder}
|
|
/>
|
|
);
|
|
}
|