mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-17 09:21:13 +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",
|
"num_threads",
|
||||||
],
|
],
|
||||||
fieldGroups: {
|
fieldGroups: {
|
||||||
detection: ["enabled", "listen", "filters"],
|
detection: ["listen", "filters"],
|
||||||
sensitivity: ["min_volume", "max_not_heard"],
|
sensitivity: ["min_volume", "max_not_heard"],
|
||||||
},
|
},
|
||||||
hiddenFields: ["enabled_in_config"],
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const detect: SectionConfigOverrides = {
|
|||||||
],
|
],
|
||||||
restartRequired: [],
|
restartRequired: [],
|
||||||
fieldGroups: {
|
fieldGroups: {
|
||||||
resolution: ["enabled", "width", "height", "fps"],
|
resolution: ["width", "height", "fps"],
|
||||||
tracking: ["min_initialized", "max_disappeared"],
|
tracking: ["min_initialized", "max_disappeared"],
|
||||||
},
|
},
|
||||||
hiddenFields: ["enabled_in_config"],
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
|||||||
@ -6,7 +6,7 @@ const faceRecognition: SectionConfigOverrides = {
|
|||||||
restartRequired: [],
|
restartRequired: [],
|
||||||
fieldOrder: ["enabled", "min_area"],
|
fieldOrder: ["enabled", "min_area"],
|
||||||
hiddenFields: [],
|
hiddenFields: [],
|
||||||
advancedFields: ["min_area"],
|
advancedFields: [],
|
||||||
overrideFields: ["enabled", "min_area"],
|
overrideFields: ["enabled", "min_area"],
|
||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
|
|||||||
@ -7,9 +7,9 @@ const lpr: SectionConfigOverrides = {
|
|||||||
enhancement: "/configuration/license_plate_recognition#enhancement",
|
enhancement: "/configuration/license_plate_recognition#enhancement",
|
||||||
},
|
},
|
||||||
restartRequired: [],
|
restartRequired: [],
|
||||||
fieldOrder: ["enabled", "expire_time", "min_area", "enhancement"],
|
fieldOrder: ["enabled", "min_area", "enhancement", "expire_time"],
|
||||||
hiddenFields: [],
|
hiddenFields: [],
|
||||||
advancedFields: ["expire_time", "min_area", "enhancement"],
|
advancedFields: ["expire_time", "enhancement"],
|
||||||
overrideFields: ["enabled", "min_area", "enhancement"],
|
overrideFields: ["enabled", "min_area", "enhancement"],
|
||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ const motion: SectionConfigOverrides = {
|
|||||||
"mqtt_off_delay",
|
"mqtt_off_delay",
|
||||||
],
|
],
|
||||||
fieldGroups: {
|
fieldGroups: {
|
||||||
sensitivity: ["enabled", "threshold", "contour_area"],
|
sensitivity: ["threshold", "contour_area"],
|
||||||
algorithm: ["improve_contrast", "delta_alpha", "frame_alpha"],
|
algorithm: ["improve_contrast", "delta_alpha", "frame_alpha"],
|
||||||
},
|
},
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const record: SectionConfigOverrides = {
|
|||||||
"export",
|
"export",
|
||||||
],
|
],
|
||||||
fieldGroups: {
|
fieldGroups: {
|
||||||
retention: ["enabled", "continuous", "motion"],
|
retention: ["continuous", "motion"],
|
||||||
events: ["alerts", "detections"],
|
events: ["alerts", "detections"],
|
||||||
},
|
},
|
||||||
hiddenFields: ["enabled_in_config", "sync_recordings"],
|
hiddenFields: ["enabled_in_config", "sync_recordings"],
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const snapshots: SectionConfigOverrides = {
|
|||||||
"retain",
|
"retain",
|
||||||
],
|
],
|
||||||
fieldGroups: {
|
fieldGroups: {
|
||||||
display: ["enabled", "bounding_box", "crop", "quality", "timestamp"],
|
display: ["bounding_box", "crop", "quality", "timestamp"],
|
||||||
},
|
},
|
||||||
hiddenFields: ["enabled_in_config"],
|
hiddenFields: ["enabled_in_config"],
|
||||||
advancedFields: ["height", "quality", "retain"],
|
advancedFields: ["height", "quality", "retain"],
|
||||||
|
|||||||
@ -311,51 +311,54 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const grouped = new Set<string>();
|
// Build a lookup: field name → group info
|
||||||
const groups = Object.entries(groupDefinitions)
|
const fieldToGroup = new Map<
|
||||||
.map(([groupKey, fields]) => {
|
string,
|
||||||
const ordered = fields
|
{ groupKey: string; label: string; items: (typeof properties)[number][] }
|
||||||
.map((field) => items.find((item) => item.name === field))
|
>();
|
||||||
.filter(Boolean) as (typeof properties)[number][];
|
const hasGroups = Object.keys(groupDefinitions).length > 0;
|
||||||
|
|
||||||
if (ordered.length === 0) {
|
for (const [groupKey, fields] of Object.entries(groupDefinitions)) {
|
||||||
return null;
|
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
|
const label = domain
|
||||||
? t(`${sectionI18nPrefix}.${domain}.${groupKey}`, {
|
? t(`${sectionI18nPrefix}.${domain}.${groupKey}`, {
|
||||||
ns: "config/groups",
|
ns: "config/groups",
|
||||||
defaultValue: humanizeKey(groupKey),
|
defaultValue: humanizeKey(groupKey),
|
||||||
})
|
})
|
||||||
: t(`groups.${groupKey}`, {
|
: t(`groups.${groupKey}`, {
|
||||||
defaultValue: humanizeKey(groupKey),
|
defaultValue: humanizeKey(groupKey),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
const groupInfo = { groupKey, label, items: ordered };
|
||||||
key: groupKey,
|
for (const item of ordered) {
|
||||||
label,
|
fieldToGroup.set(item.name, groupInfo);
|
||||||
items: ordered,
|
}
|
||||||
};
|
}
|
||||||
})
|
|
||||||
.filter(Boolean) as Array<{
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
items: (typeof properties)[number][];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
const ungrouped = items.filter((item) => !grouped.has(item.name));
|
|
||||||
const isObjectLikeField = (item: (typeof properties)[number]) => {
|
const isObjectLikeField = (item: (typeof properties)[number]) => {
|
||||||
const fieldSchema = (item.content.props as RjsfElementProps)?.schema;
|
const fieldSchema = (item.content.props as RjsfElementProps)?.schema;
|
||||||
return fieldSchema?.type === "object";
|
return fieldSchema?.type === "object";
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
// Walk items in order (respects fieldOrder / ui:order).
|
||||||
<div className="space-y-6">
|
// When we hit the first field of a group, render the whole group block.
|
||||||
{groups.map((group) => (
|
// 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
|
<div
|
||||||
key={group.key}
|
key={group.groupKey}
|
||||||
className="space-y-4 rounded-lg border border-border/70 bg-card/30 p-4"
|
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">
|
<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 key={element.name}>{element.content}</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
))}
|
);
|
||||||
|
} else {
|
||||||
|
elements.push(
|
||||||
|
<div
|
||||||
|
key={item.name}
|
||||||
|
className={cn(hasGroups && !isObjectLikeField(item) && "px-4")}
|
||||||
|
>
|
||||||
|
{item.content}
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{ungrouped.length > 0 && (
|
return <div className="space-y-6">{elements}</div>;
|
||||||
<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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Root level renders children directly
|
// Root level renders children directly
|
||||||
@ -456,7 +455,7 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<CardHeader className="cursor-pointer p-4 transition-colors hover:bg-muted/50">
|
<CardHeader className="cursor-pointer p-4 transition-colors hover:bg-muted/50">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div className="min-w-0 pr-3">
|
||||||
<CardTitle
|
<CardTitle
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center text-sm",
|
"flex items-center text-sm",
|
||||||
@ -475,9 +474,9 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isOpen ? (
|
{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>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user