This commit is contained in:
Josh Hawkins 2025-10-16 14:55:36 -05:00
parent 83354be7f6
commit 2827561238
2 changed files with 34 additions and 25 deletions

View File

@ -5,7 +5,10 @@ import { getLifecycleItemDescription } from "@/utils/lifecycleUtil";
import { useDetailStream } from "@/context/detail-stream-context";
import scrollIntoView from "scroll-into-view-if-needed";
import useUserInteraction from "@/hooks/use-user-interaction";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import {
formatUnixTimestampToDateTime,
formatSecondsToDuration,
} from "@/utils/dateUtil";
import { useTranslation } from "react-i18next";
import AnnotationOffsetSlider from "@/components/overlay/detail/AnnotationOffsetSlider";
import { FrigateConfig } from "@/types/frigateConfig";
@ -13,7 +16,7 @@ import useSWR from "swr";
import ActivityIndicator from "../indicators/activity-indicator";
import { Event } from "@/types/event";
import { getIconForLabel } from "@/utils/iconUtil";
import { ReviewSegment, REVIEW_PADDING } from "@/types/review";
import { ReviewSegment } from "@/types/review";
import {
Collapsible,
CollapsibleTrigger,
@ -49,7 +52,6 @@ export default function DetailStream({
});
const effectiveTime = currentTime + annotationOffset / 1000;
const PAD = 0; // REVIEW_PADDING ?? 2;
const [upload, setUpload] = useState<Event | undefined>(undefined);
// Ensure we initialize the active review when reviewItems first arrive.
@ -64,8 +66,8 @@ export default function DetailStream({
let closest: { r: ReviewSegment; diff: number } | undefined;
for (const r of reviewItems) {
const start = (r.start_time ?? 0) - PAD;
const end = (r.end_time ?? r.start_time ?? start) + PAD;
const start = r.start_time ?? 0;
const end = r.end_time ?? r.start_time ?? start;
if (effectiveTime >= start && effectiveTime <= end) {
target = r;
break;
@ -78,12 +80,12 @@ export default function DetailStream({
if (!target && closest) target = closest.r;
if (target) {
const start = (target.start_time ?? 0) - PAD;
const start = target.start_time ?? 0;
setActiveReviewId(
`review-${target.id ?? target.start_time ?? Math.floor(start)}`,
);
}
}, [reviewItems, activeReviewId, effectiveTime, PAD]);
}, [reviewItems, activeReviewId, effectiveTime]);
// Auto-scroll to current time
useEffect(() => {
@ -99,8 +101,8 @@ export default function DetailStream({
let closest: { r: ReviewSegment; diff: number } | undefined;
for (const r of items) {
const start = (r.start_time ?? 0) - PAD;
const end = (r.end_time ?? r.start_time ?? start) + PAD;
const start = r.start_time ?? 0;
const end = r.end_time ?? r.start_time ?? start;
if (effectiveTime >= start && effectiveTime <= end) {
target = r;
break;
@ -113,7 +115,7 @@ export default function DetailStream({
if (!target && closest) target = closest.r;
if (target) {
const start = (target.start_time ?? 0) - PAD;
const start = target.start_time ?? 0;
const id = `review-${target.id ?? target.start_time ?? Math.floor(start)}`;
const element = scrollRef.current.querySelector(
`[data-review-id="${id}"]`,
@ -132,15 +134,14 @@ export default function DetailStream({
annotationOffset,
userInteracting,
setProgrammaticScroll,
PAD,
]);
// Auto-select active review based on effectiveTime (if inside a review range)
useEffect(() => {
if (!reviewItems || reviewItems.length === 0) return;
for (const r of reviewItems) {
const start = (r.start_time ?? 0) - PAD;
const end = (r.end_time ?? r.start_time ?? start) + PAD;
const start = r.start_time ?? 0;
const end = r.end_time ?? r.start_time ?? start;
if (effectiveTime >= start && effectiveTime <= end) {
setActiveReviewId(
`review-${r.id ?? r.start_time ?? Math.floor(start)}`,
@ -148,7 +149,7 @@ export default function DetailStream({
return;
}
}
}, [effectiveTime, reviewItems, PAD]);
}, [effectiveTime, reviewItems]);
if (!config) {
return <ActivityIndicator />;
@ -173,7 +174,7 @@ export default function DetailStream({
</div>
) : (
reviewItems?.map((review: ReviewSegment) => {
const id = `review-${review.id ?? review.start_time ?? Math.floor((review.start_time ?? 0) - PAD)}`;
const id = `review-${review.id ?? review.start_time ?? Math.floor(review.start_time ?? 0)}`;
return (
<ReviewGroup
key={id}
@ -219,18 +220,14 @@ function ReviewGroup({
effectiveTime,
}: ReviewGroupProps) {
const { t } = useTranslation("views/events");
const PAD = REVIEW_PADDING ?? 2;
const start = review.start_time ?? 0;
// derive start timestamp from the review
const start = (review.start_time ?? 0) - PAD;
// display time first in the header
const displayTime = formatUnixTimestampToDateTime(start, {
timezone: config.ui.timezone,
date_format:
config.ui.time_format == "24hour"
? t("time.formattedTimestamp.24hour", { ns: "common" })
: t("time.formattedTimestamp.12hour", { ns: "common" }),
? t("time.formattedTimestampHourMinuteSecond.24hour", { ns: "common" })
: t("time.formattedTimestampHourMinuteSecond.12hour", { ns: "common" }),
time_style: "medium",
date_style: "medium",
});
@ -268,6 +265,13 @@ function ReviewGroup({
}
}, [review, t, fetchedEvents]);
const reviewDuration =
review.end_time != null
? formatSecondsToDuration(
Math.max(0, Math.floor((review.end_time ?? 0) - start)),
)
: null;
return (
<div
data-review-id={id}
@ -287,6 +291,11 @@ function ReviewGroup({
<div className="flex items-center gap-2">
<div className="flex flex-col">
<div className="text-sm font-medium">{displayTime}</div>
{reviewDuration && (
<div className="text-xs text-muted-foreground">
{reviewDuration}
</div>
)}
<div className="text-xs text-muted-foreground">{reviewInfo}</div>
</div>
</div>
@ -398,7 +407,7 @@ function EventCollapsible({
event.id != selectedObjectId &&
(effectiveTime ?? 0) >= (event.start_time ?? 0) &&
(effectiveTime ?? 0) <= (event.end_time ?? event.start_time ?? 0) &&
"bg-secondary outline-[1px] -outline-offset-[0.8px] outline-primary/40",
"bg-secondary-highlight outline-[1.5px] -outline-offset-[1.1px] outline-primary/40",
)}
>
<div className="flex w-full items-center justify-between">

View File

@ -36,8 +36,8 @@
--secondary-foreground: hsl(222.2, 17.4%, 36.2%);
--secondary-foreground: 222.2 17.4% 36.2%;
--secondary-highlight: hsl(0, 0%, 94%);
--secondary-highlight: 0 0% 94%;
--secondary-highlight: hsl(210, 17.4%, 94%);
--secondary-highlight: 210 17.4% 94%;
--neutral: hsl(0, 0%, 45.1%);
--neutral: 0 0% 45.1%;