mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-18 09:04:28 +03:00
R1 UI fix
This commit is contained in:
parent
630363ffe6
commit
f4a2021b9c
@ -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}`}>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user