mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-24 09:08:23 +03:00
fix array field template
This commit is contained in:
parent
ab727089d2
commit
0b148d66f3
@ -1123,6 +1123,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"configForm": {
|
"configForm": {
|
||||||
|
"global": {
|
||||||
|
"title": "Global Settings",
|
||||||
|
"description": "These settings apply to all cameras unless overridden in the camera-specific settings."
|
||||||
|
},
|
||||||
|
"camera": {
|
||||||
|
"title": "Camera Settings",
|
||||||
|
"description": "These settings apply only to this camera and override the global settings."
|
||||||
|
},
|
||||||
"showAdvanced": "Show Advanced Settings",
|
"showAdvanced": "Show Advanced Settings",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"sharedDefaults": "Shared Defaults",
|
"sharedDefaults": "Shared Defaults",
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const ReviewSection = createConfigSection({
|
|||||||
"detections.labels",
|
"detections.labels",
|
||||||
"detections.enabled_in_config",
|
"detections.enabled_in_config",
|
||||||
"detections.required_zones",
|
"detections.required_zones",
|
||||||
|
"genai.enabled_in_config",
|
||||||
],
|
],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { SwitchesWidget } from "./widgets/SwitchesWidget";
|
|||||||
import { ObjectLabelSwitchesWidget } from "./widgets/ObjectLabelSwitchesWidget";
|
import { ObjectLabelSwitchesWidget } from "./widgets/ObjectLabelSwitchesWidget";
|
||||||
import { AudioLabelSwitchesWidget } from "./widgets/AudioLabelSwitchesWidget";
|
import { AudioLabelSwitchesWidget } from "./widgets/AudioLabelSwitchesWidget";
|
||||||
import { ZoneSwitchesWidget } from "./widgets/ZoneSwitchesWidget";
|
import { ZoneSwitchesWidget } from "./widgets/ZoneSwitchesWidget";
|
||||||
|
import { ArrayAsTextWidget } from "./widgets/ArrayAsTextWidget";
|
||||||
|
|
||||||
import { FieldTemplate } from "./templates/FieldTemplate";
|
import { FieldTemplate } from "./templates/FieldTemplate";
|
||||||
import { ObjectFieldTemplate } from "./templates/ObjectFieldTemplate";
|
import { ObjectFieldTemplate } from "./templates/ObjectFieldTemplate";
|
||||||
@ -48,6 +49,7 @@ export const frigateTheme: FrigateTheme = {
|
|||||||
PasswordWidget: PasswordWidget,
|
PasswordWidget: PasswordWidget,
|
||||||
SelectWidget: SelectWidget,
|
SelectWidget: SelectWidget,
|
||||||
CheckboxWidget: SwitchWidget,
|
CheckboxWidget: SwitchWidget,
|
||||||
|
ArrayAsTextWidget: ArrayAsTextWidget,
|
||||||
// Custom widgets
|
// Custom widgets
|
||||||
switch: SwitchWidget,
|
switch: SwitchWidget,
|
||||||
password: PasswordWidget,
|
password: PasswordWidget,
|
||||||
|
|||||||
@ -1,19 +1,10 @@
|
|||||||
// Array Field Template - renders array fields with add/remove controls
|
// Array Field Template - renders array fields with add/remove controls
|
||||||
import type { ArrayFieldTemplateProps } from "@rjsf/utils";
|
import type { ArrayFieldTemplateProps } from "@rjsf/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { LuPlus } from "react-icons/lu";
|
||||||
import { LuPlus, LuTrash2, LuGripVertical } from "react-icons/lu";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface ArrayItem {
|
|
||||||
key: string;
|
|
||||||
index: number;
|
|
||||||
children: React.ReactNode;
|
|
||||||
hasRemove: boolean;
|
|
||||||
onDropIndexClick: (index: number) => () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
||||||
const { items, canAdd, onAddClick, disabled, readonly, schema } = props;
|
const { items, canAdd, onAddClick, disabled, readonly, schema } = props;
|
||||||
|
|
||||||
@ -36,52 +27,20 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(items as unknown as ArrayItem[]).map((element: ArrayItem) => (
|
{items.map((element, index) => {
|
||||||
<div
|
// RJSF items are pre-rendered React elements, render them directly
|
||||||
key={element.key}
|
return (
|
||||||
className={cn("flex items-start gap-2", !isSimpleType && "flex-col")}
|
<div
|
||||||
>
|
key={element.key || index}
|
||||||
{isSimpleType ? (
|
className={cn(
|
||||||
<div className="flex flex-1 items-center gap-2">
|
"flex items-start gap-2",
|
||||||
<LuGripVertical className="h-4 w-4 cursor-move text-muted-foreground" />
|
!isSimpleType && "flex-col",
|
||||||
<div className="flex-1">{element.children}</div>
|
)}
|
||||||
{element.hasRemove && (
|
>
|
||||||
<Button
|
{element}
|
||||||
type="button"
|
</div>
|
||||||
variant="ghost"
|
);
|
||||||
size="icon"
|
})}
|
||||||
onClick={element.onDropIndexClick(element.index)}
|
|
||||||
disabled={disabled || readonly}
|
|
||||||
className="h-8 w-8 text-destructive hover:text-destructive"
|
|
||||||
>
|
|
||||||
<LuTrash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Card className="w-full">
|
|
||||||
<CardContent className="pt-4">
|
|
||||||
<div className="flex items-start gap-2">
|
|
||||||
<LuGripVertical className="mt-2 h-4 w-4 cursor-move text-muted-foreground" />
|
|
||||||
<div className="flex-1">{element.children}</div>
|
|
||||||
{element.hasRemove && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={element.onDropIndexClick(element.index)}
|
|
||||||
disabled={disabled || readonly}
|
|
||||||
className="h-8 w-8 text-destructive hover:text-destructive"
|
|
||||||
>
|
|
||||||
<LuTrash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{canAdd && (
|
{canAdd && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
// Widget that displays an array as a concatenated text string
|
||||||
|
import type { WidgetProps } from "@rjsf/utils";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
export function ArrayAsTextWidget(props: WidgetProps) {
|
||||||
|
const { value, onChange, disabled, readonly, placeholder } = props;
|
||||||
|
|
||||||
|
// Convert array to space-separated string
|
||||||
|
const textValue =
|
||||||
|
Array.isArray(value) && value.length > 0 ? value.join(" ") : "";
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newText = event.target.value;
|
||||||
|
// Convert space-separated string back to array
|
||||||
|
const newArray = newText.trim() ? newText.trim().split(/\s+/) : [];
|
||||||
|
onChange(newArray);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
value={textValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={disabled}
|
||||||
|
readOnly={readonly}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -62,6 +62,7 @@ const globalSectionConfigs: Record<
|
|||||||
advancedFields?: string[];
|
advancedFields?: string[];
|
||||||
liveValidate?: boolean;
|
liveValidate?: boolean;
|
||||||
i18nNamespace: string;
|
i18nNamespace: string;
|
||||||
|
uiSchema?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
mqtt: {
|
mqtt: {
|
||||||
@ -122,6 +123,11 @@ const globalSectionConfigs: Record<
|
|||||||
"hash_iterations",
|
"hash_iterations",
|
||||||
"roles",
|
"roles",
|
||||||
],
|
],
|
||||||
|
uiSchema: {
|
||||||
|
reset_admin_password: {
|
||||||
|
"ui:widget": "switch",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tls: {
|
tls: {
|
||||||
i18nNamespace: "config/tls",
|
i18nNamespace: "config/tls",
|
||||||
@ -207,6 +213,44 @@ const globalSectionConfigs: Record<
|
|||||||
"apple_compatibility",
|
"apple_compatibility",
|
||||||
"gpu",
|
"gpu",
|
||||||
],
|
],
|
||||||
|
uiSchema: {
|
||||||
|
global_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hwaccel_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
input_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
detect: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
detectors: {
|
detectors: {
|
||||||
i18nNamespace: "config/detectors",
|
i18nNamespace: "config/detectors",
|
||||||
@ -247,10 +291,11 @@ const globalSectionConfigs: Record<
|
|||||||
"runtime_options",
|
"runtime_options",
|
||||||
],
|
],
|
||||||
advancedFields: ["base_url", "provider_options", "runtime_options"],
|
advancedFields: ["base_url", "provider_options", "runtime_options"],
|
||||||
|
hiddenFields: ["genai.enabled_in_config"],
|
||||||
},
|
},
|
||||||
classification: {
|
classification: {
|
||||||
i18nNamespace: "config/classification",
|
i18nNamespace: "config/classification",
|
||||||
fieldOrder: ["bird", "custom"],
|
hiddenFields: ["custom"],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
semantic_search: {
|
semantic_search: {
|
||||||
@ -318,11 +363,6 @@ const globalSectionConfigs: Record<
|
|||||||
"replace_rules",
|
"replace_rules",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
go2rtc: {
|
|
||||||
i18nNamespace: "config/go2rtc",
|
|
||||||
fieldOrder: [],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// System sections (global only)
|
// System sections (global only)
|
||||||
@ -341,7 +381,6 @@ const systemSections = [
|
|||||||
"detectors",
|
"detectors",
|
||||||
"model",
|
"model",
|
||||||
"classification",
|
"classification",
|
||||||
"go2rtc",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Integration sections (global only)
|
// Integration sections (global only)
|
||||||
@ -481,6 +520,7 @@ function GlobalConfigSection({
|
|||||||
hiddenFields={sectionConfig.hiddenFields}
|
hiddenFields={sectionConfig.hiddenFields}
|
||||||
advancedFields={sectionConfig.advancedFields}
|
advancedFields={sectionConfig.advancedFields}
|
||||||
liveValidate={sectionConfig.liveValidate}
|
liveValidate={sectionConfig.liveValidate}
|
||||||
|
uiSchema={sectionConfig.uiSchema}
|
||||||
showSubmit={false}
|
showSubmit={false}
|
||||||
i18nNamespace={sectionConfig.i18nNamespace}
|
i18nNamespace={sectionConfig.i18nNamespace}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user