mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-01 16:42:18 +03:00
Settings UI tweaks (#22547)
* fix genai settings ui - add roles widget to select roles for genai providers - add dropdown in semantic search to allow selection of embeddings genai provider * 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 * mobile tweaks stack buttons, add more space on profiles pane, and move the overridden badge beneath the description * language consistency * prevent camera config sections from being regenerated for profiles * conditionally import axengine module to match other detectors * i18n * update vscode launch.json for new integrated browser * formatting
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user