mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-15 08:21:16 +03:00
tweak grouping to prioritize fieldOrder before groups
previously, groups were always rendered first. now fieldOrder is respected, and any fields in a group will cause the group and all the fields in that group to be rendered in order. this allows moving the enabled switches to the top of the section
This commit is contained in:
parent
b4fb90bcbd
commit
705becba8c
@ -13,7 +13,7 @@ const audio: SectionConfigOverrides = {
|
||||
"num_threads",
|
||||
],
|
||||
fieldGroups: {
|
||||
detection: ["enabled", "listen", "filters"],
|
||||
detection: ["listen", "filters"],
|
||||
sensitivity: ["min_volume", "max_not_heard"],
|
||||
},
|
||||
hiddenFields: ["enabled_in_config"],
|
||||
|
||||
@ -18,7 +18,7 @@ const detect: SectionConfigOverrides = {
|
||||
],
|
||||
restartRequired: [],
|
||||
fieldGroups: {
|
||||
resolution: ["enabled", "width", "height", "fps"],
|
||||
resolution: ["width", "height", "fps"],
|
||||
tracking: ["min_initialized", "max_disappeared"],
|
||||
},
|
||||
hiddenFields: ["enabled_in_config"],
|
||||
|
||||
@ -6,7 +6,7 @@ const faceRecognition: SectionConfigOverrides = {
|
||||
restartRequired: [],
|
||||
fieldOrder: ["enabled", "min_area"],
|
||||
hiddenFields: [],
|
||||
advancedFields: ["min_area"],
|
||||
advancedFields: [],
|
||||
overrideFields: ["enabled", "min_area"],
|
||||
},
|
||||
global: {
|
||||
|
||||
@ -7,9 +7,9 @@ const lpr: SectionConfigOverrides = {
|
||||
enhancement: "/configuration/license_plate_recognition#enhancement",
|
||||
},
|
||||
restartRequired: [],
|
||||
fieldOrder: ["enabled", "expire_time", "min_area", "enhancement"],
|
||||
fieldOrder: ["enabled", "min_area", "enhancement", "expire_time"],
|
||||
hiddenFields: [],
|
||||
advancedFields: ["expire_time", "min_area", "enhancement"],
|
||||
advancedFields: ["expire_time", "enhancement"],
|
||||
overrideFields: ["enabled", "min_area", "enhancement"],
|
||||
},
|
||||
global: {
|
||||
|
||||
@ -23,7 +23,7 @@ const motion: SectionConfigOverrides = {
|
||||
"mqtt_off_delay",
|
||||
],
|
||||
fieldGroups: {
|
||||
sensitivity: ["enabled", "threshold", "contour_area"],
|
||||
sensitivity: ["threshold", "contour_area"],
|
||||
algorithm: ["improve_contrast", "delta_alpha", "frame_alpha"],
|
||||
},
|
||||
uiSchema: {
|
||||
|
||||
@ -15,7 +15,7 @@ const record: SectionConfigOverrides = {
|
||||
"export",
|
||||
],
|
||||
fieldGroups: {
|
||||
retention: ["enabled", "continuous", "motion"],
|
||||
retention: ["continuous", "motion"],
|
||||
events: ["alerts", "detections"],
|
||||
},
|
||||
hiddenFields: ["enabled_in_config", "sync_recordings"],
|
||||
|
||||
@ -13,7 +13,7 @@ const snapshots: SectionConfigOverrides = {
|
||||
"retain",
|
||||
],
|
||||
fieldGroups: {
|
||||
display: ["enabled", "bounding_box", "crop", "quality", "timestamp"],
|
||||
display: ["bounding_box", "crop", "quality", "timestamp"],
|
||||
},
|
||||
hiddenFields: ["enabled_in_config"],
|
||||
advancedFields: ["height", "quality", "retain"],
|
||||
|
||||
@ -311,51 +311,54 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const grouped = new Set<string>();
|
||||
const groups = Object.entries(groupDefinitions)
|
||||
.map(([groupKey, fields]) => {
|
||||
const ordered = fields
|
||||
.map((field) => items.find((item) => item.name === field))
|
||||
.filter(Boolean) as (typeof properties)[number][];
|
||||
// Build a lookup: field name → group info
|
||||
const fieldToGroup = new Map<
|
||||
string,
|
||||
{ groupKey: string; label: string; items: (typeof properties)[number][] }
|
||||
>();
|
||||
const hasGroups = Object.keys(groupDefinitions).length > 0;
|
||||
|
||||
if (ordered.length === 0) {
|
||||
return null;
|
||||
}
|
||||
for (const [groupKey, fields] of Object.entries(groupDefinitions)) {
|
||||
const ordered = fields
|
||||
.map((field) => items.find((item) => item.name === field))
|
||||
.filter(Boolean) as (typeof properties)[number][];
|
||||
|
||||
ordered.forEach((item) => grouped.add(item.name));
|
||||
if (ordered.length === 0) continue;
|
||||
|
||||
const label = domain
|
||||
? t(`${sectionI18nPrefix}.${domain}.${groupKey}`, {
|
||||
ns: "config/groups",
|
||||
defaultValue: humanizeKey(groupKey),
|
||||
})
|
||||
: t(`groups.${groupKey}`, {
|
||||
defaultValue: humanizeKey(groupKey),
|
||||
});
|
||||
const label = domain
|
||||
? t(`${sectionI18nPrefix}.${domain}.${groupKey}`, {
|
||||
ns: "config/groups",
|
||||
defaultValue: humanizeKey(groupKey),
|
||||
})
|
||||
: t(`groups.${groupKey}`, {
|
||||
defaultValue: humanizeKey(groupKey),
|
||||
});
|
||||
|
||||
return {
|
||||
key: groupKey,
|
||||
label,
|
||||
items: ordered,
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
items: (typeof properties)[number][];
|
||||
}>;
|
||||
const groupInfo = { groupKey, label, items: ordered };
|
||||
for (const item of ordered) {
|
||||
fieldToGroup.set(item.name, groupInfo);
|
||||
}
|
||||
}
|
||||
|
||||
const ungrouped = items.filter((item) => !grouped.has(item.name));
|
||||
const isObjectLikeField = (item: (typeof properties)[number]) => {
|
||||
const fieldSchema = (item.content.props as RjsfElementProps)?.schema;
|
||||
return fieldSchema?.type === "object";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{groups.map((group) => (
|
||||
// Walk items in order (respects fieldOrder / ui:order).
|
||||
// When we hit the first field of a group, render the whole group block.
|
||||
// Skip subsequent fields that belong to an already-rendered group.
|
||||
const renderedGroups = new Set<string>();
|
||||
const elements: React.ReactNode[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
const group = fieldToGroup.get(item.name);
|
||||
if (group) {
|
||||
if (renderedGroups.has(group.groupKey)) continue;
|
||||
renderedGroups.add(group.groupKey);
|
||||
elements.push(
|
||||
<div
|
||||
key={group.key}
|
||||
key={group.groupKey}
|
||||
className="space-y-4 rounded-lg border border-border/70 bg-card/30 p-4"
|
||||
>
|
||||
<div className="text-md border-b border-border/60 pb-4 font-semibold text-primary-variant">
|
||||
@ -366,25 +369,21 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
||||
<div key={element.name}>{element.content}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>,
|
||||
);
|
||||
} else {
|
||||
elements.push(
|
||||
<div
|
||||
key={item.name}
|
||||
className={cn(hasGroups && !isObjectLikeField(item) && "px-4")}
|
||||
>
|
||||
{item.content}
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{ungrouped.length > 0 && (
|
||||
<div className={cn("space-y-6", groups.length > 0 && "pt-2")}>
|
||||
{ungrouped.map((element) => (
|
||||
<div
|
||||
key={element.name}
|
||||
className={cn(
|
||||
groups.length > 0 && !isObjectLikeField(element) && "px-4",
|
||||
)}
|
||||
>
|
||||
{element.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return <div className="space-y-6">{elements}</div>;
|
||||
};
|
||||
|
||||
// Root level renders children directly
|
||||
@ -456,7 +455,7 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
||||
<CollapsibleTrigger asChild>
|
||||
<CardHeader className="cursor-pointer p-4 transition-colors hover:bg-muted/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="min-w-0 pr-3">
|
||||
<CardTitle
|
||||
className={cn(
|
||||
"flex items-center text-sm",
|
||||
@ -475,9 +474,9 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
||||
)}
|
||||
</div>
|
||||
{isOpen ? (
|
||||
<LuChevronDown className="h-4 w-4" />
|
||||
<LuChevronDown className="h-4 w-4 shrink-0" />
|
||||
) : (
|
||||
<LuChevronRight className="h-4 w-4" />
|
||||
<LuChevronRight className="h-4 w-4 shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user