make object lifecycle scrollable in tracking details

This commit is contained in:
Josh Hawkins 2025-12-20 07:51:29 -06:00
parent 081e47f6d7
commit 73cd48262f
2 changed files with 102 additions and 101 deletions

View File

@ -599,9 +599,14 @@ export default function SearchDetailDialog({
<Content <Content
ref={isDesktop ? dialogContentRef : undefined} ref={isDesktop ? dialogContentRef : undefined}
className={cn( className={cn(
"scrollbar-container overflow-y-auto", isDesktop && [
isDesktop && "max-h-[95dvh] max-w-[85%] xl:max-w-[70%]", "max-h-[95dvh] max-w-[85%] xl:max-w-[70%]",
isMobile && "flex h-full flex-col px-4", pageToggle === "tracking_details"
? "flex flex-col overflow-hidden"
: "scrollbar-container overflow-y-auto",
],
isMobile &&
"scrollbar-container flex h-full flex-col overflow-y-auto px-4",
)} )}
onEscapeKeyDown={(event) => { onEscapeKeyDown={(event) => {
if (isPopoverOpen) { if (isPopoverOpen) {

View File

@ -526,7 +526,7 @@ export function TrackingDetails({
<div <div
className={cn( className={cn(
"flex items-center justify-center", "flex items-start justify-center",
isDesktop && "overflow-hidden", isDesktop && "overflow-hidden",
cameraAspect === "tall" ? "max-h-[50dvh] lg:max-h-[70dvh]" : "w-full", cameraAspect === "tall" ? "max-h-[50dvh] lg:max-h-[70dvh]" : "w-full",
cameraAspect === "tall" && isMobileOnly && "w-full", cameraAspect === "tall" && isMobileOnly && "w-full",
@ -622,7 +622,10 @@ export function TrackingDetails({
<div <div
className={cn( className={cn(
isDesktop && "justify-between overflow-hidden lg:basis-2/5", isDesktop && "justify-start overflow-hidden",
aspectRatio > 1 && aspectRatio < 1.5
? "lg:basis-3/5"
: "lg:basis-2/5",
)} )}
> >
{isDesktop && tabs && ( {isDesktop && tabs && (
@ -632,121 +635,114 @@ export function TrackingDetails({
)} )}
<div <div
className={cn( className={cn(
isDesktop && "scrollbar-container h-full overflow-y-auto", isDesktop && "scrollbar-container max-h-[70vh] overflow-y-auto",
)} )}
> >
{config?.cameras[event.camera]?.onvif.autotracking {config?.cameras[event.camera]?.onvif.autotracking
.enabled_in_config && ( .enabled_in_config && (
<div className="mb-2 ml-3 text-sm text-danger"> <div className="mb-4 ml-3 text-sm text-danger">
{t("trackingDetails.autoTrackingTips")} {t("trackingDetails.autoTrackingTips")}
</div> </div>
)} )}
<div className="mt-4"> <div className={cn("rounded-md bg-background_alt px-0 py-3 md:px-2")}>
<div <div className="flex w-full items-center justify-between">
className={cn("rounded-md bg-background_alt px-0 py-3 md:px-2")} <div
> className="flex items-center gap-2 font-medium"
<div className="flex w-full items-center justify-between"> onClick={(e) => {
e.stopPropagation();
// event.start_time is detect time, convert to record
handleSeekToTime(
(event.start_time ?? 0) + annotationOffset / 1000,
);
}}
role="button"
>
<div <div
className="flex items-center gap-2 font-medium" className={cn(
onClick={(e) => { "relative ml-2 rounded-full bg-muted-foreground p-2",
e.stopPropagation(); )}
// event.start_time is detect time, convert to record
handleSeekToTime(
(event.start_time ?? 0) + annotationOffset / 1000,
);
}}
role="button"
> >
<div {getIconForLabel(
className={cn( event.sub_label ? event.label + "-verified" : event.label,
"relative ml-2 rounded-full bg-muted-foreground p-2", "size-4 text-white",
)} )}
> </div>
{getIconForLabel( <div className="flex items-center gap-2">
event.sub_label ? event.label + "-verified" : event.label, <span className="capitalize">{label}</span>
"size-4 text-white", <div className="md:text-md flex items-center text-xs text-secondary-foreground">
)} {formattedStart ?? ""}
</div> {event.end_time != null ? (
<div className="flex items-center gap-2"> <> - {formattedEnd}</>
<span className="capitalize">{label}</span> ) : (
<div className="md:text-md flex items-center text-xs text-secondary-foreground"> <div className="inline-block">
{formattedStart ?? ""} <ActivityIndicator className="ml-3 size-4" />
{event.end_time != null ? ( </div>
<> - {formattedEnd}</>
) : (
<div className="inline-block">
<ActivityIndicator className="ml-3 size-4" />
</div>
)}
</div>
{event.data?.recognized_license_plate && (
<>
<span className="text-secondary-foreground">·</span>
<div className="text-sm text-secondary-foreground">
<Link
to={`/explore?recognized_license_plate=${event.data.recognized_license_plate}`}
className="text-sm"
>
{event.data.recognized_license_plate}
</Link>
</div>
</>
)} )}
</div> </div>
{event.data?.recognized_license_plate && (
<>
<span className="text-secondary-foreground">·</span>
<div className="text-sm text-secondary-foreground">
<Link
to={`/explore?recognized_license_plate=${event.data.recognized_license_plate}`}
className="text-sm"
>
{event.data.recognized_license_plate}
</Link>
</div>
</>
)}
</div> </div>
</div> </div>
</div>
<div className="mt-2"> <div className="mt-2">
{!eventSequence ? ( {!eventSequence ? (
<ActivityIndicator className="size-2" size={2} /> <ActivityIndicator className="size-2" size={2} />
) : eventSequence.length === 0 ? ( ) : eventSequence.length === 0 ? (
<div className="py-2 text-muted-foreground"> <div className="py-2 text-muted-foreground">
{t("detail.noObjectDetailData", { ns: "views/events" })} {t("detail.noObjectDetailData", { ns: "views/events" })}
</div> </div>
) : ( ) : (
<div className="-pb-2 relative mx-0" ref={timelineContainerRef}>
<div <div
className="-pb-2 relative mx-0" className="absolute -top-2 left-6 z-0 w-0.5 -translate-x-1/2 bg-secondary-foreground"
ref={timelineContainerRef} style={{ bottom: lineBottomOffsetPx }}
> />
{isWithinEventRange && (
<div <div
className="absolute -top-2 left-6 z-0 w-0.5 -translate-x-1/2 bg-secondary-foreground" className="absolute left-6 z-[5] w-0.5 -translate-x-1/2 bg-selected transition-all duration-300"
style={{ bottom: lineBottomOffsetPx }} style={{
top: `${lineTopOffsetPx}px`,
height: `${blueLineHeightPx}px`,
}}
/> />
{isWithinEventRange && ( )}
<div <div className="space-y-2">
className="absolute left-6 z-[5] w-0.5 -translate-x-1/2 bg-selected transition-all duration-300" {eventSequence.map((item, idx) => {
style={{ return (
top: `${lineTopOffsetPx}px`, <div
height: `${blueLineHeightPx}px`, key={`${item.timestamp}-${item.source_id ?? ""}-${idx}`}
}} ref={(el) => {
/> rowRefs.current[idx] = el;
)} }}
<div className="space-y-2"> >
{eventSequence.map((item, idx) => { <LifecycleIconRow
return ( item={item}
<div event={event}
key={`${item.timestamp}-${item.source_id ?? ""}-${idx}`} onClick={() => handleLifecycleClick(item)}
ref={(el) => { setSelectedZone={setSelectedZone}
rowRefs.current[idx] = el; getZoneColor={getZoneColor}
}} effectiveTime={effectiveTime}
> isTimelineActive={isWithinEventRange}
<LifecycleIconRow />
item={item} </div>
event={event} );
onClick={() => handleLifecycleClick(item)} })}
setSelectedZone={setSelectedZone}
getZoneColor={getZoneColor}
effectiveTime={effectiveTime}
isTimelineActive={isWithinEventRange}
/>
</div>
);
})}
</div>
</div> </div>
)} </div>
</div> )}
</div> </div>
</div> </div>
</div> </div>