Handle in progress review items

This commit is contained in:
Nicolas Mowen 2024-04-10 09:37:26 -06:00
parent 5ac79a7a4b
commit 94d64920c4
5 changed files with 56 additions and 26 deletions

View File

@ -21,11 +21,14 @@ import { useSwipeable } from "react-swipeable";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator"; import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator";
import useContextMenu from "@/hooks/use-contextmenu"; import useContextMenu from "@/hooks/use-contextmenu";
import ActivityIndicator from "../indicators/activity-indicator";
import { TimeRange } from "@/types/timeline";
type PreviewPlayerProps = { type PreviewPlayerProps = {
review: ReviewSegment; review: ReviewSegment;
allPreviews?: Preview[]; allPreviews?: Preview[];
scrollLock?: boolean; scrollLock?: boolean;
timeRange: TimeRange;
onTimeUpdate?: (time: number | undefined) => void; onTimeUpdate?: (time: number | undefined) => void;
setReviewed: (review: ReviewSegment) => void; setReviewed: (review: ReviewSegment) => void;
onClick: (review: ReviewSegment, ctrl: boolean) => void; onClick: (review: ReviewSegment, ctrl: boolean) => void;
@ -43,6 +46,7 @@ export default function PreviewThumbnailPlayer({
review, review,
allPreviews, allPreviews,
scrollLock = false, scrollLock = false,
timeRange,
setReviewed, setReviewed,
onClick, onClick,
onTimeUpdate, onTimeUpdate,
@ -70,8 +74,10 @@ export default function PreviewThumbnailPlayer({
}); });
const handleSetReviewed = useCallback(() => { const handleSetReviewed = useCallback(() => {
review.has_been_reviewed = true; if (review.end_time) {
setReviewed(review); review.has_been_reviewed = true;
setReviewed(review);
}
}, [review, setReviewed]); }, [review, setReviewed]);
useContextMenu(imgRef, () => { useContextMenu(imgRef, () => {
@ -91,7 +97,7 @@ export default function PreviewThumbnailPlayer({
return false; return false;
} }
if (review.end_time > preview.end) { if ((review.end_time ?? timeRange.before) > preview.end) {
multiHour = true; multiHour = true;
} }
@ -108,7 +114,8 @@ export default function PreviewThumbnailPlayer({
const firstPrev = allPreviews[firstIndex]; const firstPrev = allPreviews[firstIndex];
const firstDuration = firstPrev.end - review.start_time; const firstDuration = firstPrev.end - review.start_time;
const secondDuration = review.end_time - firstPrev.end; const secondDuration =
(review.end_time ?? timeRange.before) - firstPrev.end;
if (firstDuration > secondDuration) { if (firstDuration > secondDuration) {
// the first preview is longer than the second, return the first // the first preview is longer than the second, return the first
@ -123,7 +130,7 @@ export default function PreviewThumbnailPlayer({
return undefined; return undefined;
} }
}, [allPreviews, review]); }, [allPreviews, review, timeRange]);
// Hover Playback // Hover Playback
@ -183,6 +190,7 @@ export default function PreviewThumbnailPlayer({
<PreviewContent <PreviewContent
review={review} review={review}
relevantPreview={relevantPreview} relevantPreview={relevantPreview}
timeRange={timeRange}
setReviewed={handleSetReviewed} setReviewed={handleSetReviewed}
setIgnoreClick={setIgnoreClick} setIgnoreClick={setIgnoreClick}
isPlayingBack={setPlayback} isPlayingBack={setPlayback}
@ -256,7 +264,13 @@ export default function PreviewThumbnailPlayer({
<div className="absolute top-0 inset-x-0 rounded-t-l z-10 w-full h-[30%] bg-gradient-to-b from-black/60 to-transparent pointer-events-none"></div> <div className="absolute top-0 inset-x-0 rounded-t-l z-10 w-full h-[30%] bg-gradient-to-b from-black/60 to-transparent pointer-events-none"></div>
<div className="absolute bottom-0 inset-x-0 rounded-b-l z-10 w-full h-[20%] bg-gradient-to-t from-black/60 to-transparent pointer-events-none"> <div className="absolute bottom-0 inset-x-0 rounded-b-l z-10 w-full h-[20%] bg-gradient-to-t from-black/60 to-transparent pointer-events-none">
<div className="flex h-full justify-between items-end mx-3 pb-1 text-white text-sm"> <div className="flex h-full justify-between items-end mx-3 pb-1 text-white text-sm">
<TimeAgo time={review.start_time * 1000} dense /> {review.end_time ? (
<TimeAgo time={review.start_time * 1000} dense />
) : (
<div>
<ActivityIndicator size={24} />
</div>
)}
{formattedDate} {formattedDate}
</div> </div>
</div> </div>
@ -270,6 +284,7 @@ export default function PreviewThumbnailPlayer({
type PreviewContentProps = { type PreviewContentProps = {
review: ReviewSegment; review: ReviewSegment;
relevantPreview: Preview | undefined; relevantPreview: Preview | undefined;
timeRange: TimeRange;
setReviewed: () => void; setReviewed: () => void;
setIgnoreClick: (ignore: boolean) => void; setIgnoreClick: (ignore: boolean) => void;
isPlayingBack: (ended: boolean) => void; isPlayingBack: (ended: boolean) => void;
@ -278,6 +293,7 @@ type PreviewContentProps = {
function PreviewContent({ function PreviewContent({
review, review,
relevantPreview, relevantPreview,
timeRange,
setReviewed, setReviewed,
setIgnoreClick, setIgnoreClick,
isPlayingBack, isPlayingBack,
@ -300,6 +316,7 @@ function PreviewContent({
return ( return (
<InProgressPreview <InProgressPreview
review={review} review={review}
timeRange={timeRange}
setReviewed={setReviewed} setReviewed={setReviewed}
setIgnoreClick={setIgnoreClick} setIgnoreClick={setIgnoreClick}
isPlayingBack={isPlayingBack} isPlayingBack={isPlayingBack}
@ -348,7 +365,10 @@ function VideoPreview({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const playerDuration = useMemo( const playerDuration = useMemo(
() => review.end_time - review.start_time + PREVIEW_PADDING, () =>
(review.end_time ?? relevantPreview.end) -
review.start_time +
PREVIEW_PADDING,
// we know that these deps are correct // we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[], [],
@ -551,6 +571,7 @@ function VideoPreview({
const MIN_LOAD_TIMEOUT_MS = 200; const MIN_LOAD_TIMEOUT_MS = 200;
type InProgressPreviewProps = { type InProgressPreviewProps = {
review: ReviewSegment; review: ReviewSegment;
timeRange: TimeRange;
setReviewed: (reviewId: string) => void; setReviewed: (reviewId: string) => void;
setIgnoreClick: (ignore: boolean) => void; setIgnoreClick: (ignore: boolean) => void;
isPlayingBack: (ended: boolean) => void; isPlayingBack: (ended: boolean) => void;
@ -558,6 +579,7 @@ type InProgressPreviewProps = {
}; };
function InProgressPreview({ function InProgressPreview({
review, review,
timeRange,
setReviewed, setReviewed,
setIgnoreClick, setIgnoreClick,
isPlayingBack, isPlayingBack,
@ -567,7 +589,7 @@ function InProgressPreview({
const sliderRef = useRef<HTMLDivElement | null>(null); const sliderRef = useRef<HTMLDivElement | null>(null);
const { data: previewFrames } = useSWR<string[]>( const { data: previewFrames } = useSWR<string[]>(
`preview/${review.camera}/start/${Math.floor(review.start_time) - PREVIEW_PADDING}/end/${ `preview/${review.camera}/start/${Math.floor(review.start_time) - PREVIEW_PADDING}/end/${
Math.ceil(review.end_time) + PREVIEW_PADDING Math.ceil(review.end_time ?? timeRange.before) + PREVIEW_PADDING
}/frames`, }/frames`,
{ revalidateOnFocus: false }, { revalidateOnFocus: false },
); );

View File

@ -204,7 +204,7 @@ export default function Events() {
const newData = [...data]; const newData = [...data];
newData.forEach((seg) => { newData.forEach((seg) => {
if (seg.severity == severity) { if (seg.end_time && seg.severity == severity) {
seg.has_been_reviewed = true; seg.has_been_reviewed = true;
} }
}); });
@ -214,10 +214,16 @@ export default function Events() {
{ revalidate: false, populateCache: true }, { revalidate: false, populateCache: true },
); );
await axios.post(`reviews/viewed`, { const itemsToMarkReviewed = currentItems
ids: currentItems?.map((seg) => seg.id), ?.filter((seg) => seg.end_time)
}); ?.map((seg) => seg.id);
reloadData();
if (itemsToMarkReviewed.length > 0) {
await axios.post(`reviews/viewed`, {
ids: itemsToMarkReviewed,
});
reloadData();
}
}, },
[reloadData, updateSegments], [reloadData, updateSegments],
); );

View File

@ -3,7 +3,7 @@ export interface ReviewSegment {
camera: string; camera: string;
severity: ReviewSeverity; severity: ReviewSeverity;
start_time: number; start_time: number;
end_time: number; end_time?: number;
thumb_path: string; thumb_path: string;
has_been_reviewed: boolean; has_been_reviewed: boolean;
data: ReviewData; data: ReviewData;

View File

@ -546,6 +546,7 @@ function DetectionReview({
<PreviewThumbnailPlayer <PreviewThumbnailPlayer
review={value} review={value}
allPreviews={relevantPreviews} allPreviews={relevantPreviews}
timeRange={timeRange}
setReviewed={markItemAsReviewed} setReviewed={markItemAsReviewed}
scrollLock={scrollLock} scrollLock={scrollLock}
onTimeUpdate={onPreviewTimeUpdate} onTimeUpdate={onPreviewTimeUpdate}
@ -787,16 +788,16 @@ function MotionReview({
} else { } else {
const segmentStartTime = alignStartDateToTimeline(currentTime); const segmentStartTime = alignStartDateToTimeline(currentTime);
const segmentEndTime = segmentStartTime + segmentDuration; const segmentEndTime = segmentStartTime + segmentDuration;
const matchingItem = reviewItems?.all.find( const matchingItem = reviewItems?.all.find((item) => {
(item) => const endTime = item.end_time ?? timeRange.before;
((item.start_time >= segmentStartTime &&
item.start_time < segmentEndTime) || ((item.start_time >= segmentStartTime &&
(item.end_time > segmentStartTime && item.start_time < segmentEndTime) ||
item.end_time <= segmentEndTime) || (endTime > segmentStartTime && endTime <= segmentEndTime) ||
(item.start_time <= segmentStartTime && (item.start_time <= segmentStartTime &&
item.end_time >= segmentEndTime)) && endTime >= segmentEndTime)) &&
item.camera === cameraName, item.camera === cameraName;
); });
return matchingItem ? matchingItem.severity : null; return matchingItem ? matchingItem.severity : null;
} }
@ -805,6 +806,7 @@ function MotionReview({
reviewItems, reviewItems,
motionData, motionData,
currentTime, currentTime,
timeRange,
motionOnly, motionOnly,
alignStartDateToTimeline, alignStartDateToTimeline,
], ],

View File

@ -47,8 +47,8 @@ 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.type == "end" && eventUpdate.review.severity == "alert") { if (eventUpdate.review.severity == "alert") {
setTimeout(() => updateEvents(), 1000); setTimeout(() => updateEvents(), eventUpdate.type == "end" ? 1000 : 6000);
return; return;
} }
}, [eventUpdate, updateEvents]); }, [eventUpdate, updateEvents]);