{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 &&
}
-
- );
- })
- ) : (
-
- )}
-
- >
- );
-}