diff --git a/web/src/components/filter/FilterCheckBox.tsx b/web/src/components/filter/FilterCheckBox.tsx
index b23a4e42c..3e8ee21cd 100644
--- a/web/src/components/filter/FilterCheckBox.tsx
+++ b/web/src/components/filter/FilterCheckBox.tsx
@@ -5,6 +5,7 @@ import { IconType } from "react-icons";
type FilterCheckBoxProps = {
label: string;
CheckIcon?: IconType;
+ iconClassName?: string;
isChecked: boolean;
onCheckedChange: (isChecked: boolean) => void;
};
@@ -12,6 +13,7 @@ type FilterCheckBoxProps = {
export default function FilterCheckBox({
label,
CheckIcon = LuCheck,
+ iconClassName = "size-6",
isChecked,
onCheckedChange,
}: FilterCheckBoxProps) {
@@ -22,9 +24,9 @@ export default function FilterCheckBox({
onClick={() => onCheckedChange(!isChecked)}
>
{isChecked ? (
-
+
) : (
-
+
)}
{label}
diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx
index 12434f67e..893fbacc6 100644
--- a/web/src/components/filter/ReviewFilterGroup.tsx
+++ b/web/src/components/filter/ReviewFilterGroup.tsx
@@ -280,7 +280,7 @@ type CalendarFilterButtonProps = {
day?: Date;
updateSelectedDay: (day?: Date) => void;
};
-function CalendarFilterButton({
+export function CalendarFilterButton({
reviewSummary,
day,
updateSelectedDay,
diff --git a/web/src/components/image/AnimatedEventThumbnail.tsx b/web/src/components/image/AnimatedEventThumbnail.tsx
index b990de5b7..dad85116a 100644
--- a/web/src/components/image/AnimatedEventThumbnail.tsx
+++ b/web/src/components/image/AnimatedEventThumbnail.tsx
@@ -7,6 +7,7 @@ import { FrigateConfig } from "@/types/frigateConfig";
import { ReviewSegment } from "@/types/review";
import { useNavigate } from "react-router-dom";
import { Skeleton } from "../ui/skeleton";
+import { RecordingStartingPoint } from "@/types/record";
type AnimatedEventThumbnailProps = {
event: ReviewSegment;
@@ -18,7 +19,13 @@ export function AnimatedEventThumbnail({ event }: AnimatedEventThumbnailProps) {
const navigate = useNavigate();
const onOpenReview = useCallback(() => {
- navigate("events", { state: { review: event.id } });
+ navigate("events", {
+ state: {
+ camera: event.camera,
+ startTime: event.start_time,
+ severity: event.severity,
+ } as RecordingStartingPoint,
+ });
}, [navigate, event]);
// image behavior
diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx
index 74652b57c..0ddecdfea 100644
--- a/web/src/pages/Events.tsx
+++ b/web/src/pages/Events.tsx
@@ -36,11 +36,18 @@ export default function Events() {
const [reviewFilter, setReviewFilter, reviewSearchParams] =
useApiFilter();
- const onUpdateFilter = useCallback((newFilter: ReviewFilter) => {
- setReviewFilter(newFilter);
- // we don't want this updating
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ const onUpdateFilter = useCallback(
+ (newFilter: ReviewFilter) => {
+ setReviewFilter(newFilter);
+
+ // update recording start time if filter
+ // was changed on recording page
+ if (recording != undefined && newFilter.after != undefined) {
+ setRecording({ ...recording, startTime: newFilter.after }, true);
+ }
+ },
+ [recording, setRecording, setReviewFilter],
+ );
// review paging
@@ -286,10 +293,8 @@ export default function Events() {
return {
camera: recording.camera,
- severity: recording.severity,
start_time: recording.startTime,
allCameras: allCameras,
- cameraSegments: reviews.filter((seg) => allCameras.includes(seg.camera)),
};
// previews will not update after item is selected
@@ -306,9 +311,11 @@ export default function Events() {
startCamera={selectedReviewData.camera}
startTime={selectedReviewData.start_time}
allCameras={selectedReviewData.allCameras}
- severity={selectedReviewData.severity}
- reviewItems={selectedReviewData.cameraSegments}
+ reviewItems={reviews}
+ reviewSummary={reviewSummary}
allPreviews={allPreviews}
+ filter={reviewFilter}
+ updateFilter={onUpdateFilter}
/>
);
} else {
diff --git a/web/src/views/events/RecordingView.tsx b/web/src/views/events/RecordingView.tsx
index 4f167400b..2c5b3fa61 100644
--- a/web/src/views/events/RecordingView.tsx
+++ b/web/src/views/events/RecordingView.tsx
@@ -1,24 +1,26 @@
+import FilterCheckBox from "@/components/filter/FilterCheckBox";
+import { CalendarFilterButton } from "@/components/filter/ReviewFilterGroup";
import PreviewPlayer, {
PreviewController,
} from "@/components/player/PreviewPlayer";
import { DynamicVideoController } from "@/components/player/dynamic/DynamicVideoController";
import DynamicVideoPlayer from "@/components/player/dynamic/DynamicVideoPlayer";
-import EventReviewTimeline from "@/components/timeline/EventReviewTimeline";
import MotionReviewTimeline from "@/components/timeline/MotionReviewTimeline";
import { Button } from "@/components/ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuRadioGroup,
- DropdownMenuRadioItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
+import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import { FrigateConfig } from "@/types/frigateConfig";
import { Preview } from "@/types/preview";
-import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
+import {
+ MotionData,
+ ReviewFilter,
+ ReviewSegment,
+ ReviewSummary,
+} from "@/types/review";
+import { getEndOfDayTimestamp } from "@/utils/dateUtil";
import { getChunkedTimeDay } from "@/utils/timelineUtil";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { isDesktop, isMobile } from "react-device-detect";
+import { FaCircle, FaVideo } from "react-icons/fa";
import { IoMdArrowRoundBack } from "react-icons/io";
import { useNavigate } from "react-router-dom";
import useSWR from "swr";
@@ -28,18 +30,22 @@ const SEGMENT_DURATION = 30;
type RecordingViewProps = {
startCamera: string;
startTime: number;
- severity: ReviewSeverity;
- reviewItems: ReviewSegment[];
+ reviewItems?: ReviewSegment[];
+ reviewSummary?: ReviewSummary;
allCameras: string[];
allPreviews?: Preview[];
+ filter?: ReviewFilter;
+ updateFilter: (newFilter: ReviewFilter) => void;
};
export function RecordingView({
startCamera,
startTime,
- severity,
reviewItems,
+ reviewSummary,
allCameras,
allPreviews,
+ filter,
+ updateFilter,
}: RecordingViewProps) {
const { data: config } = useSWR("config");
const navigate = useNavigate();
@@ -54,7 +60,7 @@ export function RecordingView({
const [playbackStart, setPlaybackStart] = useState(startTime);
const mainCameraReviewItems = useMemo(
- () => reviewItems.filter((cam) => cam.camera == mainCamera),
+ () => reviewItems?.filter((cam) => cam.camera == mainCamera) ?? [],
[reviewItems, mainCamera],
);
@@ -157,19 +163,15 @@ export function RecordingView({
// motion timeline data
- const { data: motionData } = useSWR(
- severity == "significant_motion"
- ? [
- "review/activity/motion",
- {
- before: timeRange.end,
- after: timeRange.start,
- scale: SEGMENT_DURATION / 2,
- cameras: mainCamera,
- },
- ]
- : null,
- );
+ const { data: motionData } = useSWR([
+ "review/activity/motion",
+ {
+ before: timeRange.end,
+ after: timeRange.start,
+ scale: SEGMENT_DURATION / 2,
+ cameras: mainCamera,
+ },
+ ]);
const mainCameraAspect = useMemo(() => {
if (!config) {
@@ -201,41 +203,61 @@ export function RecordingView({
return (
-