R1 UI fix

This commit is contained in:
Weitheng Haw 2025-02-04 11:23:14 +00:00
parent 630363ffe6
commit f4a2021b9c
2 changed files with 90 additions and 113 deletions

View File

@ -32,7 +32,7 @@ import {
MobilePageTitle,
} from "@/components/mobile/MobilePage";
const LPR_TABS = ["details", "snapshot", "video"] as const;
const LPR_TABS = ["details", "snapshot", "video", "raw"] as const;
export type LPRTab = (typeof LPR_TABS)[number];
type LPRDetailDialogProps = {
@ -41,6 +41,7 @@ type LPRDetailDialogProps = {
event?: Event;
config: FrigateConfig;
lprImage: string;
rawImage: string;
};
export default function LPRDetailDialog({
@ -49,6 +50,7 @@ export default function LPRDetailDialog({
event,
config,
lprImage,
rawImage,
}: LPRDetailDialogProps) {
const [page, setPage] = useState<LPRTab>("details");
@ -66,12 +68,9 @@ export default function LPRDetailDialog({
);
const lprTabs = useMemo(() => {
if (!config || !event) {
return [];
}
const views = [...LPR_TABS];
if (event) {
if (!event.has_snapshot) {
const index = views.indexOf("snapshot");
views.splice(index, 1);
@ -81,6 +80,10 @@ export default function LPRDetailDialog({
const index = views.indexOf("video");
views.splice(index, 1);
}
} else {
// When no event, show only raw tab
return ['raw'];
}
return views;
}, [config, event]);
@ -100,7 +103,7 @@ export default function LPRDetailDialog({
<Title>License Plate Image</Title>
<Description className="sr-only">License plate image details</Description>
</Header>
<PlateTab lprImage={lprImage} />
<PlateTab lprImage={lprImage} config={config} />
</Content>
</Overlay>
);
@ -110,19 +113,19 @@ export default function LPRDetailDialog({
<Overlay open={open} onOpenChange={setOpen}>
<Content
className={cn(
"scrollbar-container overflow-y-auto min-w-[50vw]",
"scrollbar-container overflow-y-auto",
isDesktop &&
"max-h-[95dvh] sm:max-w-xl md:max-w-4xl lg:max-w-4xl xl:max-w-7xl",
isMobile && "px-4",
)}
>
<Header>
<Title>License Plate Event Details</Title>
<Description className="sr-only">License plate event details</Description>
<Title>{event ? "License Plate Event Details" : "License Plate Image"}</Title>
<Description className="sr-only">License plate details</Description>
</Header>
<ScrollArea
className={cn("w-full whitespace-nowrap", isMobile && "my-2")}
>
{event && (
<ScrollArea className={cn("w-full whitespace-nowrap", isMobile && "my-2")}>
<div className="flex flex-row">
<ToggleGroup
className="*:rounded-md *:px-3 *:py-4"
@ -135,17 +138,20 @@ export default function LPRDetailDialog({
}
}}
>
{Object.values(lprTabs).map((item) => (
{lprTabs.map((item) => (
<ToggleGroupItem
key={item}
className={`flex scroll-mx-10 items-center justify-between gap-2 ${page == "details" ? "last:mr-20" : ""} ${page == item ? "" : "*:text-muted-foreground"}`}
className={`flex scroll-mx-10 items-center justify-between gap-2 ${
page === item ? "" : "*:text-muted-foreground"
}`}
value={item}
data-nav-item={item}
aria-label={`Select ${item}`}
>
{item == "details" && <FaRegListAlt className="size-4" />}
{item == "snapshot" && <FaImage className="size-4" />}
{item == "video" && <FaVideo className="size-4" />}
{item === "details" && <FaRegListAlt className="size-4" />}
{item === "snapshot" && <FaImage className="size-4" />}
{item === "video" && <FaVideo className="size-4" />}
{item === "raw" && <FaImage className="size-4" />}
<div className="capitalize">{item}</div>
</ToggleGroupItem>
))}
@ -153,17 +159,22 @@ export default function LPRDetailDialog({
<ScrollBar orientation="horizontal" className="h-0" />
</div>
</ScrollArea>
{page === "details" && (
<DetailsTab event={event} timestamp={timestamp} />
)}
{page === "snapshot" && (
<SnapshotTab event={event} />
{event ? (
<>
{page === "details" && <DetailsTab event={event} timestamp={timestamp} />}
{page === "snapshot" && <SnapshotTab event={event} />}
{page === "video" && <VideoTab event={event} />}
{page === "raw" && (
<PlateTab
lprImage={rawImage}
config={config}
/>
)}
{page === "video" && (
<VideoTab event={event} />
)}
{(page === "details" || !event) && (
<PlateTab lprImage={lprImage} />
</>
) : (
<PlateTab lprImage={lprImage} config={config} />
)}
</Content>
</Overlay>
@ -333,9 +344,10 @@ function VideoTab({ event }: VideoTabProps) {
type PlateTabProps = {
lprImage: string;
config: FrigateConfig;
};
function PlateTab({ lprImage }: PlateTabProps) {
function PlateTab({ lprImage, config }: PlateTabProps) {
const [imgRef, imgLoaded, onImgLoad] = useImageLoaded();
return (
@ -346,33 +358,31 @@ function PlateTab({ lprImage }: PlateTabProps) {
/>
<div className={`${imgLoaded ? "visible" : "invisible"} min-h-[60dvh]`}>
<TransformWrapper minScale={1.0} wheel={{ smoothStep: 0.005 }}>
<div className="flex flex-col space-y-3">
<div className="flex flex-col space-y-3 h-full">
<TransformComponent
wrapperStyle={{
width: "100%",
height: "100%",
}}
contentStyle={{
position: "relative",
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
contentStyle={{
position: "relative",
width: "100%",
height: "100%",
}}
>
<div className="relative mx-auto w-full h-full flex items-center justify-center">
<div className="relative w-full h-full flex items-center justify-center">
<img
ref={imgRef}
className="mx-auto max-h-[60dvh] w-auto h-auto bg-black object-contain"
className="max-h-[70vh] w-auto object-contain bg-black"
src={`${baseUrl}clips/lpr/${lprImage}`}
alt="License plate"
loading={isSafari ? "eager" : "lazy"}
onLoad={() => {
onImgLoad();
}}
onLoad={onImgLoad}
/>
<div className={cn("absolute right-1 top-1 flex items-center gap-2")}>
<div className="absolute right-2 top-2 flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<a download href={`${baseUrl}clips/lpr/${lprImage}`}>

View File

@ -31,7 +31,6 @@ export default function LPRDebug() {
const { data: config } = useSWR<FrigateConfig>("config");
const [sortBy, setSortBy] = useState<SortOption>("time_desc");
const [selectedCameras, setSelectedCameras] = useState<string[] | undefined>();
const [viewMode, setViewMode] = useState<ViewMode>("detected");
// title
useEffect(() => {
@ -109,40 +108,6 @@ export default function LPRDebug() {
<div className="relative mb-2 flex h-11 w-full items-center justify-between">
<ScrollArea className="w-full whitespace-nowrap">
<div className="flex flex-row">
<ToggleGroup
className="*:rounded-md *:px-3 *:py-4"
type="single"
size="sm"
value={viewMode}
onValueChange={(value: ViewMode) => {
if (value) {
setViewMode(value);
}
}}
>
<ToggleGroupItem
value="detected"
className={cn(
"flex scroll-mx-10 items-center justify-between gap-2",
viewMode !== "detected" && "*:text-muted-foreground"
)}
data-nav-item="detected"
aria-label="Select detected"
>
<div>Detected</div>
</ToggleGroupItem>
<ToggleGroupItem
value="raw"
className={cn(
"flex scroll-mx-10 items-center justify-between gap-2",
viewMode !== "raw" && "*:text-muted-foreground"
)}
data-nav-item="raw"
aria-label="Select raw"
>
<div>Raw</div>
</ToggleGroupItem>
</ToggleGroup>
<ScrollBar orientation="horizontal" className="h-0" />
</div>
</ScrollArea>
@ -178,14 +143,13 @@ export default function LPRDebug() {
</DropdownMenu>
</div>
</div>
<div className="scrollbar-container flex flex-wrap gap-2 overflow-y-scroll">
<div className="scrollbar-container grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2 overflow-y-auto">
{lprAttempts.map((attempt: string) => (
<LPRAttempt
key={attempt}
attempt={attempt}
config={config}
onRefresh={refreshLPR}
viewMode={viewMode}
/>
))}
</div>
@ -197,10 +161,9 @@ type LPRAttemptProps = {
attempt: string;
config: FrigateConfig;
onRefresh: () => void;
viewMode: ViewMode;
};
function LPRAttempt({ attempt, config, onRefresh, viewMode }: LPRAttemptProps) {
function LPRAttempt({ attempt, config, onRefresh }: LPRAttemptProps) {
const [showDialog, setShowDialog] = useState(false);
const data = useMemo(() => {
const parts = attempt.split("_");
@ -245,6 +208,9 @@ function LPRAttempt({ attempt, config, onRefresh, viewMode }: LPRAttemptProps) {
});
}, [attempt, onRefresh]);
// Extract event ID from processed image filename (format: PLATE_SCORE_EVENTID.jpg)
const eventId = useMemo(() => attempt.split("_").slice(2).join("_").replace(".jpg", ""), [attempt]);
return (
<>
<LPRDetailDialog
@ -252,7 +218,8 @@ function LPRAttempt({ attempt, config, onRefresh, viewMode }: LPRAttemptProps) {
setOpen={setShowDialog}
event={event}
config={config}
lprImage={viewMode === "detected" ? attempt : `raw_${data.eventId}.jpg`}
lprImage={attempt}
rawImage={`raw_${eventId}.jpg`}
/>
<div className="relative flex flex-col rounded-lg">
@ -263,7 +230,7 @@ function LPRAttempt({ attempt, config, onRefresh, viewMode }: LPRAttemptProps) {
<div className="aspect-[2/1] flex items-center justify-center bg-black">
<img
className="h-40 max-w-none"
src={`${baseUrl}clips/lpr/${viewMode === "detected" ? attempt : `raw_${data.eventId}.jpg`}`}
src={`${baseUrl}clips/lpr/${attempt}`}
/>
</div>
</div>