diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index 4c68813fe..38b1d1bee 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -1,3 +1,4 @@ +import { useFrigateEvents } from "@/api/ws"; import useApiFilter from "@/hooks/use-api-filter"; import useOverlayState from "@/hooks/use-overlay-state"; import { ReviewFilter, ReviewSegment } from "@/types/review"; @@ -5,7 +6,7 @@ import DesktopEventView from "@/views/events/DesktopEventView"; import DesktopRecordingView from "@/views/events/DesktopRecordingView"; import MobileEventView from "@/views/events/MobileEventView"; import axios from "axios"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { isMobile } from "react-device-detect"; import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; @@ -183,6 +184,25 @@ export default function Events() { }; }, [selectedReviewId, reviewPages]); + // review updates + + const { payload: eventUpdate } = useFrigateEvents(); + const [hasUpdate, setHasUpdate] = useState(false); + useEffect(() => { + if (!eventUpdate) { + return; + } + + // if event is ended and was saved, update events list + if ( + eventUpdate.type == "end" && + (eventUpdate.after.has_clip || eventUpdate.after.has_snapshot) + ) { + setHasUpdate(true); + return; + } + }, [eventUpdate]); + if (selectedData) { return ( ); } @@ -213,6 +236,8 @@ export default function Events() { reachedEnd={isDone} isValidating={isValidating} filter={reviewFilter} + hasUpdate={hasUpdate} + setHasUpdate={setHasUpdate} loadNextPage={onLoadNextPage} markItemAsReviewed={markItemAsReviewed} onSelectReview={setSelectedReviewId} diff --git a/web/src/views/events/DesktopEventView.tsx b/web/src/views/events/DesktopEventView.tsx index d31fefbbf..804b2f15e 100644 --- a/web/src/views/events/DesktopEventView.tsx +++ b/web/src/views/events/DesktopEventView.tsx @@ -1,4 +1,3 @@ -import { useFrigateEvents } from "@/api/ws"; import ReviewFilterGroup from "@/components/filter/ReviewFilterGroup"; import PreviewThumbnailPlayer from "@/components/player/PreviewThumbnailPlayer"; import EventReviewTimeline from "@/components/timeline/EventReviewTimeline"; @@ -19,6 +18,8 @@ type DesktopEventViewProps = { reachedEnd: boolean; isValidating: boolean; filter?: ReviewFilter; + hasUpdate: boolean; + setHasUpdate: (hasUpdated: boolean) => void; loadNextPage: () => void; markItemAsReviewed: (reviewId: string) => void; onSelectReview: (reviewId: string) => void; @@ -32,6 +33,8 @@ export default function DesktopEventView({ reachedEnd, isValidating, filter, + hasUpdate, + setHasUpdate, loadNextPage, markItemAsReviewed, onSelectReview, @@ -177,25 +180,6 @@ export default function DesktopEventView({ return data; }, [minimap]); - // new data alert - - const { payload: eventUpdate } = useFrigateEvents(); - const [hasUpdate, setHasUpdate] = useState(false); - useEffect(() => { - if (!eventUpdate) { - return; - } - - // if event is ended and was saved, update events list - if ( - eventUpdate.type == "end" && - (eventUpdate.after.has_clip || eventUpdate.after.has_snapshot) - ) { - setHasUpdate(true); - return; - } - }, [eventUpdate]); - if (!config) { return ; } @@ -305,7 +289,7 @@ export default function DesktopEventView({ review={value} relevantPreview={relevantPreview} setReviewed={markItemAsReviewed} - onClick={onSelectReview(value.id} + onClick={onSelectReview} /> {lastRow && !reachedEnd && } diff --git a/web/src/views/events/MobileEventView.tsx b/web/src/views/events/MobileEventView.tsx index 75622013f..b476808d7 100644 --- a/web/src/views/events/MobileEventView.tsx +++ b/web/src/views/events/MobileEventView.tsx @@ -1,9 +1,11 @@ import PreviewThumbnailPlayer from "@/components/player/PreviewThumbnailPlayer"; import ActivityIndicator from "@/components/ui/activity-indicator"; +import { Button } from "@/components/ui/button"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { FrigateConfig } from "@/types/frigateConfig"; import { ReviewSegment, ReviewSeverity } from "@/types/review"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { LuRefreshCcw } from "react-icons/lu"; import { MdCircle } from "react-icons/md"; import useSWR from "swr"; @@ -12,16 +14,22 @@ type MobileEventViewProps = { relevantPreviews?: Preview[]; reachedEnd: boolean; isValidating: boolean; + hasUpdate: boolean; + setHasUpdate: (hasUpdated: boolean) => void; loadNextPage: () => void; markItemAsReviewed: (reviewId: string) => void; + pullLatestData: () => void; }; export default function MobileEventView({ reviewPages, relevantPreviews, reachedEnd, isValidating, + hasUpdate, + setHasUpdate, loadNextPage, markItemAsReviewed, + pullLatestData, }: MobileEventViewProps) { const { data: config } = useSWR("config"); const [severity, setSeverity] = useState("alert"); @@ -198,6 +206,34 @@ export default function MobileEventView({ + {hasUpdate && ( +
+
+ +
+
+ )} +