Compare commits

..

2 Commits

Author SHA1 Message Date
Nicolas Mowen
24a1874225
UI Tweaks (#20403)
Some checks failed
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
Stalebot / stale (push) Has been cancelled
* Fix context menu link to debug

* Use genai title for tooltip when available
2025-10-09 09:49:42 -05:00
Josh Hawkins
f4e7549311
UI tweaks (#20401)
* font changes to better match figma

* fix alignment in debug view
2025-10-09 06:23:03 -06:00
16 changed files with 103 additions and 99 deletions

View File

@ -50,6 +50,27 @@ export function AnimatedEventCard({
fetchPreviews: !currentHour, fetchPreviews: !currentHour,
}); });
const tooltipText = useMemo(() => {
if (event?.data?.metadata?.title) {
return event.data.metadata.title;
}
return (
`${[
...new Set([
...(event.data.objects || []),
...(event.data.sub_labels || []),
...(event.data.audio || []),
]),
]
.filter((item) => item !== undefined && !item.includes("-verified"))
.map((text) => text.charAt(0).toUpperCase() + text.substring(1))
.sort()
.join(", ")
.replaceAll("-verified", "")} ` + t("detected")
);
}, [event, t]);
// visibility // visibility
const [windowVisible, setWindowVisible] = useState(true); const [windowVisible, setWindowVisible] = useState(true);
@ -220,20 +241,7 @@ export function AnimatedEventCard({
)} )}
</div> </div>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>{tooltipText}</TooltipContent>
{`${[
...new Set([
...(event.data.objects || []),
...(event.data.sub_labels || []),
...(event.data.audio || []),
]),
]
.filter((item) => item !== undefined && !item.includes("-verified"))
.map((text) => text.charAt(0).toUpperCase() + text.substring(1))
.sort()
.join(", ")
.replaceAll("-verified", "")} ` + t("detected")}
</TooltipContent>
</Tooltip> </Tooltip>
); );
} }

View File

@ -355,9 +355,7 @@ export default function LiveContextMenu({
<div <div
className="flex w-full cursor-pointer items-center justify-start gap-2" className="flex w-full cursor-pointer items-center justify-start gap-2"
onClick={ onClick={
isEnabled isEnabled ? () => navigate(`?debug=true#${camera}`) : undefined
? () => navigate(`/settings?page=debug&camera=${camera}`)
: undefined
} }
> >
<div className="text-primary"> <div className="text-primary">

View File

@ -222,7 +222,7 @@ export function MobilePageHeader({
type MobilePageTitleProps = React.HTMLAttributes<HTMLHeadingElement>; type MobilePageTitleProps = React.HTMLAttributes<HTMLHeadingElement>;
export function MobilePageTitle({ className, ...props }: MobilePageTitleProps) { export function MobilePageTitle({ className, ...props }: MobilePageTitleProps) {
return <h2 className={cn("text-lg font-semibold", className)} {...props} />; return <h2 className={cn("text-lg", className)} {...props} />;
} }
type MobilePageDescriptionProps = React.HTMLAttributes<HTMLParagraphElement>; type MobilePageDescriptionProps = React.HTMLAttributes<HTMLParagraphElement>;

View File

@ -42,7 +42,7 @@ export default function RoleChangeDialog({
<Dialog open={show} onOpenChange={onCancel}> <Dialog open={show} onOpenChange={onCancel}>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="sm:max-w-[425px]">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-xl font-semibold"> <DialogTitle className="text-xl">
{t("users.dialog.changeRole.title")} {t("users.dialog.changeRole.title")}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>

View File

@ -1170,11 +1170,7 @@ export function ObjectSnapshotTab({
<Card className="p-1 text-sm md:p-2"> <Card className="p-1 text-sm md:p-2">
<CardContent className="flex flex-col items-center justify-between gap-3 p-2 md:flex-row"> <CardContent className="flex flex-col items-center justify-between gap-3 p-2 md:flex-row">
<div className={cn("flex flex-col space-y-3")}> <div className={cn("flex flex-col space-y-3")}>
<div <div className={"text-lg leading-none"}>
className={
"text-lg font-semibold leading-none tracking-tight"
}
>
{t("explore.plus.submitToPlus.label")} {t("explore.plus.submitToPlus.label")}
</div> </div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">

View File

@ -1,7 +1,7 @@
import * as React from "react" import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const alertVariants = cva( const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
@ -16,8 +16,8 @@ const alertVariants = cva(
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} },
) );
const Alert = React.forwardRef< const Alert = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -29,8 +29,8 @@ const Alert = React.forwardRef<
className={cn(alertVariants({ variant }), className)} className={cn(alertVariants({ variant }), className)}
{...props} {...props}
/> />
)) ));
Alert.displayName = "Alert" Alert.displayName = "Alert";
const AlertTitle = React.forwardRef< const AlertTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -38,11 +38,11 @@ const AlertTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<h5 <h5
ref={ref} ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)} className={cn("mb-1 font-medium leading-none", className)}
{...props} {...props}
/> />
)) ));
AlertTitle.displayName = "AlertTitle" AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef< const AlertDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
className={cn("text-sm [&_p]:leading-relaxed", className)} className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props} {...props}
/> />
)) ));
AlertDescription.displayName = "AlertDescription" AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription } export { Alert, AlertTitle, AlertDescription };

View File

@ -1,6 +1,6 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Card = React.forwardRef< const Card = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -10,12 +10,12 @@ const Card = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm", "rounded-lg border bg-card text-card-foreground shadow-sm",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Card.displayName = "Card" Card.displayName = "Card";
const CardHeader = React.forwardRef< const CardHeader = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -26,8 +26,8 @@ const CardHeader = React.forwardRef<
className={cn("flex flex-col space-y-1.5 p-6", className)} className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props} {...props}
/> />
)) ));
CardHeader.displayName = "CardHeader" CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -35,14 +35,11 @@ const CardTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<h3 <h3
ref={ref} ref={ref}
className={cn( className={cn("text-2xl font-semibold leading-none", className)}
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props} {...props}
/> />
)) ));
CardTitle.displayName = "CardTitle" CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -53,16 +50,16 @@ const CardDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
CardDescription.displayName = "CardDescription" CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef< const CardContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
)) ));
CardContent.displayName = "CardContent" CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef< const CardFooter = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -73,7 +70,14 @@ const CardFooter = React.forwardRef<
className={cn("flex items-center p-6 pt-0", className)} className={cn("flex items-center p-6 pt-0", className)}
{...props} {...props}
/> />
)) ));
CardFooter.displayName = "CardFooter" CardFooter.displayName = "CardFooter";
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@ -149,10 +149,7 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref} ref={ref}
className={cn( className={cn("text-lg font-semibold leading-none", className)}
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props} {...props}
/> />
)); ));

View File

@ -85,10 +85,7 @@ const DrawerTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DrawerPrimitive.Title <DrawerPrimitive.Title
ref={ref} ref={ref}
className={cn( className={cn("text-lg font-semibold leading-none", className)}
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props} {...props}
/> />
)); ));

View File

@ -14,12 +14,7 @@ const Heading = ({
switch (as) { switch (as) {
case "h1": case "h1":
return ( return (
<h1 <h1 className={cn("scroll-m-20 text-3xl font-extrabold", className)}>
className={cn(
"scroll-m-20 text-3xl font-extrabold tracking-tight",
className
)}
>
{children} {children}
</h1> </h1>
); );
@ -27,8 +22,8 @@ const Heading = ({
return ( return (
<h2 <h2
className={cn( className={cn(
"scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0 mb-3", "mb-3 scroll-m-20 text-3xl font-semibold transition-colors first:mt-0",
className className,
)} )}
> >
{children} {children}
@ -36,34 +31,19 @@ const Heading = ({
); );
case "h3": case "h3":
return ( return (
<h3 <h3 className={cn("mb-3 scroll-m-20 text-2xl font-medium", className)}>
className={cn(
"scroll-m-20 text-2xl font-semibold tracking-tight mb-3",
className
)}
>
{children} {children}
</h3> </h3>
); );
case "h4": case "h4":
return ( return (
<h4 <h4 className={cn("scroll-m-20 text-xl font-medium", className)}>
className={cn(
"scroll-m-20 text-xl font-semibold tracking-tight",
className
)}
>
{children} {children}
</h4> </h4>
); );
default: default:
return ( return (
<h1 <h1 className={cn("scroll-m-20 text-3xl font-extrabold", className)}>
className={cn(
"scroll-m-20 text-3xl font-extrabold tracking-tight",
className
)}
>
{children} {children}
</h1> </h1>
); );

View File

@ -112,7 +112,8 @@ export function useSearchEffect(
callback: (value: string) => boolean, callback: (value: string) => boolean,
) { ) {
const location = useLocation(); const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate();
const [searchParams] = useSearchParams();
const param = useMemo(() => { const param = useMemo(() => {
const param = searchParams.get(key); const param = searchParams.get(key);
@ -132,7 +133,17 @@ export function useSearchEffect(
const remove = callback(param[1]); const remove = callback(param[1]);
if (remove) { if (remove) {
setSearchParams(undefined, { state: location.state, replace: true }); navigate(location.pathname + location.hash, {
state: location.state,
replace: true,
});
} }
}, [param, location.state, callback, setSearchParams]); }, [
param,
location.state,
location.pathname,
location.hash,
callback,
navigate,
]);
} }

View File

@ -292,7 +292,7 @@ export default function Settings() {
<Logo className="h-8" /> <Logo className="h-8" />
</div> </div>
<div className="flex flex-row text-center"> <div className="flex flex-row text-center">
<h2 className="ml-2 text-lg font-semibold"> <h2 className="ml-2 text-lg">
{t("menu.settings", { ns: "common" })} {t("menu.settings", { ns: "common" })}
</h2> </h2>
</div> </div>

View File

@ -30,7 +30,7 @@ type FrigateObjectState = {
}; };
export interface FrigateReview { export interface FrigateReview {
type: "new" | "update" | "end"; type: "new" | "update" | "end" | "genai";
before: ReviewSegment; before: ReviewSegment;
after: ReviewSegment; after: ReviewSegment;
} }

View File

@ -111,6 +111,7 @@ import { Trans, useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain"; import { useDocDomain } from "@/hooks/use-doc-domain";
import PtzControlPanel from "@/components/overlay/PtzControlPanel"; import PtzControlPanel from "@/components/overlay/PtzControlPanel";
import ObjectSettingsView from "../settings/ObjectSettingsView"; import ObjectSettingsView from "../settings/ObjectSettingsView";
import { useSearchEffect } from "@/hooks/use-overlay-state";
type LiveCameraViewProps = { type LiveCameraViewProps = {
config?: FrigateConfig; config?: FrigateConfig;
@ -274,6 +275,14 @@ export default function LiveCameraView({
const [showStats, setShowStats] = useState(false); const [showStats, setShowStats] = useState(false);
const [debug, setDebug] = useState(false); const [debug, setDebug] = useState(false);
useSearchEffect("debug", (value: string) => {
if (value === "true") {
setDebug(true);
}
return true;
});
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({ const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
width: 0, width: 0,
height: 0, height: 0,

View File

@ -114,7 +114,11 @@ export default function LiveDashboardView({
// if event is ended and was saved, update events list // if event is ended and was saved, update events list
if (eventUpdate.after.severity == "alert") { if (eventUpdate.after.severity == "alert") {
if (eventUpdate.type == "end" || eventUpdate.type == "new") { if (
eventUpdate.type == "end" ||
eventUpdate.type == "new" ||
eventUpdate.type == "genai"
) {
setTimeout( setTimeout(
() => updateEvents(), () => updateEvents(),
eventUpdate.type == "end" ? 1000 : 6000, eventUpdate.type == "end" ? 1000 : 6000,

View File

@ -162,9 +162,9 @@ export default function ObjectSettingsView({
} }
return ( return (
<div className="flex size-full flex-col md:flex-row"> <div className="mt-1 flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:w-3/12"> <div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0 md:w-3/12">
<Heading as="h4" className="mb-2"> <Heading as="h4" className="mb-2">
{t("debug.title")} {t("debug.title")}
</Heading> </Heading>