From a6bb39f17bd3ea04f1be3e6f4f15d7eceff8e864 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 27 Feb 2024 12:41:19 -0700 Subject: [PATCH] Combine event views --- web/src/pages/Events.tsx | 22 +- web/src/pages/Live.tsx | 9 +- .../{DesktopEventView.tsx => EventView.tsx} | 32 ++- web/src/views/events/MobileEventView.tsx | 248 ------------------ 4 files changed, 26 insertions(+), 285 deletions(-) rename web/src/views/events/{DesktopEventView.tsx => EventView.tsx} (89%) delete mode 100644 web/src/views/events/MobileEventView.tsx diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index e7f0b456d..f3d185c16 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -1,12 +1,10 @@ import useApiFilter from "@/hooks/use-api-filter"; import useOverlayState from "@/hooks/use-overlay-state"; import { ReviewFilter, ReviewSegment, ReviewSeverity } from "@/types/review"; -import DesktopEventView from "@/views/events/DesktopEventView"; import DesktopRecordingView from "@/views/events/DesktopRecordingView"; -import MobileEventView from "@/views/events/MobileEventView"; +import EventView from "@/views/events/EventView"; import axios from "axios"; import { useCallback, useMemo, useState } from "react"; -import { isMobile } from "react-device-detect"; import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; @@ -209,24 +207,8 @@ export default function Events() { /> ); } else { - if (isMobile) { - return ( - - ); - } - return ( - {isMobile && (
- +
@@ -129,7 +128,7 @@ function Live() { )}
{cameras.map((camera) => { let grow; diff --git a/web/src/views/events/DesktopEventView.tsx b/web/src/views/events/EventView.tsx similarity index 89% rename from web/src/views/events/DesktopEventView.tsx rename to web/src/views/events/EventView.tsx index b4741c483..4be06dea7 100644 --- a/web/src/views/events/DesktopEventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -1,3 +1,4 @@ +import Logo from "@/components/Logo"; import NewReviewData from "@/components/dynamic/NewReviewData"; import ReviewFilterGroup from "@/components/filter/ReviewFilterGroup"; import PreviewThumbnailPlayer from "@/components/player/PreviewThumbnailPlayer"; @@ -8,11 +9,12 @@ import { useEventUtils } from "@/hooks/use-event-utils"; import { FrigateConfig } from "@/types/frigateConfig"; import { ReviewFilter, ReviewSegment, ReviewSeverity } from "@/types/review"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { isDesktop, isMobile } from "react-device-detect"; import { LuFolderCheck } from "react-icons/lu"; import { MdCircle } from "react-icons/md"; import useSWR from "swr"; -type DesktopEventViewProps = { +type EventViewProps = { reviewPages?: ReviewSegment[][]; relevantPreviews?: Preview[]; timeRange: { before: number; after: number }; @@ -27,7 +29,7 @@ type DesktopEventViewProps = { pullLatestData: () => void; updateFilter: (filter: ReviewFilter) => void; }; -export default function DesktopEventView({ +export default function EventView({ reviewPages, relevantPreviews, timeRange, @@ -41,7 +43,7 @@ export default function DesktopEventView({ onSelectReview, pullLatestData, updateFilter, -}: DesktopEventViewProps) { +}: EventViewProps) { const { data: config } = useSWR("config"); const contentRef = useRef(null); const segmentDuration = 60; @@ -192,7 +194,8 @@ export default function DesktopEventView({ return (
-
+
+ - - Alerts + +
Alerts
- - Detections + +
Detections
- - Motion + +
Motion
- + {isDesktop && ( + + )}
@@ -284,6 +289,9 @@ export default function DesktopEventView({ relevantPreview={relevantPreview} setReviewed={markItemAsReviewed} onClick={onSelectReview} + autoPlayback={ + isMobile && minimapBounds.end == value.start_time + } />
{lastRow && !reachedEnd && } @@ -295,7 +303,7 @@ export default function DesktopEventView({ )}
-
+
void; - loadNextPage: () => void; - markItemAsReviewed: (reviewId: string) => void; - pullLatestData: () => void; -}; -export default function MobileEventView({ - reviewPages, - relevantPreviews, - reachedEnd, - isValidating, - severity, - setSeverity, - loadNextPage, - markItemAsReviewed, - pullLatestData, -}: MobileEventViewProps) { - const { data: config } = useSWR("config"); - const contentRef = useRef(null); - - // review paging - - const reviewItems = useMemo(() => { - const all: ReviewSegment[] = []; - const alerts: ReviewSegment[] = []; - const detections: ReviewSegment[] = []; - const motion: ReviewSegment[] = []; - - reviewPages?.forEach((page) => { - page.forEach((segment) => { - all.push(segment); - - switch (segment.severity) { - case "alert": - alerts.push(segment); - break; - case "detection": - detections.push(segment); - break; - default: - motion.push(segment); - break; - } - }); - }); - - return { - all: all, - alert: alerts, - detection: detections, - significant_motion: motion, - }; - }, [reviewPages]); - - const currentItems = useMemo(() => { - const current = reviewItems[severity]; - - if (!current || current.length == 0) { - return null; - } - - return current; - }, [reviewItems, severity]); - - // review interaction - - const pagingObserver = useRef(); - const lastReviewRef = useCallback( - (node: HTMLElement | null) => { - if (isValidating) return; - if (pagingObserver.current) pagingObserver.current.disconnect(); - try { - pagingObserver.current = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting && !reachedEnd) { - loadNextPage(); - } - }); - if (node) pagingObserver.current.observe(node); - } catch (e) { - // no op - } - }, - [isValidating, reachedEnd] - ); - - const [minimap, setMinimap] = useState([]); - const minimapObserver = useRef(); - useEffect(() => { - const visibleTimestamps = new Set(); - minimapObserver.current = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - const start = (entry.target as HTMLElement).dataset.start; - - if (!start) { - return; - } - - if (entry.isIntersecting) { - visibleTimestamps.add(start); - } else { - visibleTimestamps.delete(start); - } - - setMinimap([...visibleTimestamps]); - }); - }, - { threshold: 0.5 } - ); - - return () => { - minimapObserver.current?.disconnect(); - }; - }, []); - const minimapRef = useCallback( - (node: HTMLElement | null) => { - if (!minimapObserver.current) { - return; - } - - try { - if (node) minimapObserver.current.observe(node); - } catch (e) { - // no op - } - }, - [minimapObserver.current] - ); - const minimapBounds = useMemo(() => { - const data = { - start: 0, - end: 0, - }; - const list = minimap.sort(); - - if (list.length > 0) { - data.end = parseFloat(list.at(-1)!!); - data.start = parseFloat(list[0]); - } - - return data; - }, [minimap]); - - if (!config) { - return ; - } - - return ( - <> - setSeverity(value)} - > - - - Alerts - - - - Detections - - - - Motion - - - - - -
- {currentItems ? ( - currentItems.map((value, segIdx) => { - const lastRow = segIdx == currentItems.length - 1; - const relevantPreview = Object.values(relevantPreviews || []).find( - (preview) => - preview.camera == value.camera && - preview.start < value.start_time && - preview.end > value.end_time - ); - - return ( -
-
- -
- {lastRow && !reachedEnd && } -
- ); - }) - ) : ( -
- )} -
- - ); -}