From 09824c19a4db65bf1bcd6964d0dfde88adb7520a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 9 Sep 2024 17:20:15 -0600 Subject: [PATCH] Fix current hour search preview --- .../components/filter/SearchFilterGroup.tsx | 265 ++++++++++++------ .../player/PreviewThumbnailPlayer.tsx | 4 +- .../player/SearchThumbnailPlayer.tsx | 20 +- .../components/preview/ScrubbablePreview.tsx | 33 ++- 4 files changed, 223 insertions(+), 99 deletions(-) diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index cee34e65d..07adae66f 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -20,7 +20,14 @@ import { cn } from "@/lib/utils"; import SubFilterIcon from "../icons/SubFilterIcon"; import { FaLocationDot } from "react-icons/fa6"; -const SEARCH_FILTERS = ["cameras", "date", "general", "zone", "sub"] as const; +const SEARCH_FILTERS = [ + "cameras", + "date", + "general", + "zone", + "sub", + "source", +] as const; type SearchFilters = (typeof SEARCH_FILTERS)[number]; const DEFAULT_REVIEW_FILTERS: SearchFilters[] = [ "cameras", @@ -28,6 +35,7 @@ const DEFAULT_REVIEW_FILTERS: SearchFilters[] = [ "general", "zone", "sub", + "source", ]; type SearchFilterGroupProps = { @@ -175,15 +183,9 @@ export default function SearchFilterGroup({ { onUpdateFilter({ ...filter, labels: newLabels }); }} - updateSearchSourceFilter={(newSearchSource) => - onUpdateFilter({ ...filter, search_type: newSearchSource }) - } /> )} {filters.includes("zone") && allZones.length > 0 && ( @@ -204,6 +206,16 @@ export default function SearchFilterGroup({ } /> )} + {config?.semantic_search?.enabled && filters.includes("source") && ( + + onUpdateFilter({ ...filter, search_type: newSearchSource }) + } + /> + )} ); } @@ -211,24 +223,17 @@ export default function SearchFilterGroup({ type GeneralFilterButtonProps = { allLabels: string[]; selectedLabels: string[] | undefined; - selectedSearchSources: SearchSource[]; updateLabelFilter: (labels: string[] | undefined) => void; - updateSearchSourceFilter: (sources: SearchSource[]) => void; }; function GeneralFilterButton({ allLabels, selectedLabels, - selectedSearchSources, updateLabelFilter, - updateSearchSourceFilter, }: GeneralFilterButtonProps) { const [open, setOpen] = useState(false); const [currentLabels, setCurrentLabels] = useState( selectedLabels, ); - const [currentSearchSources, setCurrentSearchSources] = useState< - SearchSource[] - >(selectedSearchSources); const trigger = ( + ); + const content = ( + setOpen(false)} + /> + ); + + if (isMobile) { + return ( + { + if (!open) { + setCurrentSearchSources(selectedSearchSources); + } + + setOpen(open); + }} + > + {trigger} + + {content} + + + ); + } + + return ( + { + if (!open) { + setCurrentSearchSources(selectedSearchSources); + } + + setOpen(open); + }} + > + {trigger} + {content} + + ); +} + +type SearchTypeContentProps = { + selectedSearchSources: SearchSource[]; + currentSearchSources: SearchSource[]; + setCurrentSearchSources: (sources: SearchSource[]) => void; + updateSearchSourceFilter: (sources: SearchSource[]) => void; + onClose: () => void; +}; +export function SearchTypeContent({ + selectedSearchSources, + currentSearchSources, + setCurrentSearchSources, + updateSearchSourceFilter, + onClose, +}: SearchTypeContentProps) { + return ( + <> +
+
+ { + const updatedSources = currentSearchSources + ? [...currentSearchSources] + : []; + + if (isChecked) { + updatedSources.push("thumbnail"); + setCurrentSearchSources(updatedSources); + } else { + if (updatedSources.length > 1) { + const index = updatedSources.indexOf("thumbnail"); + if (index !== -1) updatedSources.splice(index, 1); + setCurrentSearchSources(updatedSources); + } + } + }} + /> + { + const updatedSources = currentSearchSources + ? [...currentSearchSources] + : []; + + if (isChecked) { + updatedSources.push("description"); + setCurrentSearchSources(updatedSources); + } else { + if (updatedSources.length > 1) { + const index = updatedSources.indexOf("description"); + if (index !== -1) updatedSources.splice(index, 1); + setCurrentSearchSources(updatedSources); + } + } + }} + /> +
+ +
+ + +
+
+ + ); +} diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index 2d91c5e3d..409618b86 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -329,7 +329,9 @@ function PreviewContent({ } else if (isCurrentHour(review.start_time)) { return ( Date.now() / 1000, []); if (relevantPreview) { return ( @@ -287,6 +288,21 @@ function PreviewContent({ /> ); } else if (isCurrentHour(searchResult.start_time)) { - return
; + return ( + {}} + /> + ); } } diff --git a/web/src/components/preview/ScrubbablePreview.tsx b/web/src/components/preview/ScrubbablePreview.tsx index eb9a962a8..f3aa4f158 100644 --- a/web/src/components/preview/ScrubbablePreview.tsx +++ b/web/src/components/preview/ScrubbablePreview.tsx @@ -6,7 +6,6 @@ import React, { useState, } from "react"; import { useApiHost } from "@/api"; -import { ReviewSegment } from "@/types/review"; import useSWR from "swr"; import { isFirefox, isMobile, isSafari } from "react-device-detect"; import { TimelineScrubMode, TimeRange } from "@/types/timeline"; @@ -286,21 +285,27 @@ export function VideoPreview({ const MIN_LOAD_TIMEOUT_MS = 200; type InProgressPreviewProps = { - review: ReviewSegment; + camera: string; + startTime: number; + endTime?: number; timeRange: TimeRange; showProgress?: boolean; loop?: boolean; - setReviewed: (reviewId: string) => void; + defaultImageUrl?: string; + setReviewed: () => void; setIgnoreClick: (ignore: boolean) => void; isPlayingBack: (ended: boolean) => void; onTimeUpdate?: (time: number | undefined) => void; windowVisible: boolean; }; export function InProgressPreview({ - review, + camera, + startTime, + endTime, timeRange, showProgress = true, loop = false, + defaultImageUrl, setReviewed, setIgnoreClick, isPlayingBack, @@ -310,8 +315,8 @@ export function InProgressPreview({ const apiHost = useApiHost(); const sliderRef = useRef(null); const { data: previewFrames } = useSWR( - `preview/${review.camera}/start/${Math.floor(review.start_time) - PREVIEW_PADDING}/end/${ - Math.ceil(review.end_time ?? timeRange.before) + PREVIEW_PADDING + `preview/${camera}/start/${Math.floor(startTime) - PREVIEW_PADDING}/end/${ + Math.ceil(endTime ?? timeRange.before) + PREVIEW_PADDING }/frames`, { revalidateOnFocus: false }, ); @@ -326,7 +331,7 @@ export function InProgressPreview({ } if (onTimeUpdate) { - onTimeUpdate(review.start_time - PREVIEW_PADDING + key); + onTimeUpdate(startTime - PREVIEW_PADDING + key); } if (playbackMode != "auto") { @@ -334,9 +339,7 @@ export function InProgressPreview({ } if (key == previewFrames.length - 1) { - if (!review.has_been_reviewed) { - setReviewed(review.id); - } + setReviewed(); if (loop) { setKey(0); @@ -356,7 +359,7 @@ export function InProgressPreview({ setTimeout(() => { if (setReviewed && key == Math.floor(previewFrames.length / 2)) { - setReviewed(review.id); + setReviewed(); } if (previewFrames[key + 1]) { @@ -377,11 +380,7 @@ export function InProgressPreview({ const onManualSeek = useCallback( (values: number[]) => { const value = values[0]; - - if (!review.has_been_reviewed) { - setReviewed(review.id); - } - + setReviewed(); setKey(value); }, @@ -424,7 +423,7 @@ export function InProgressPreview({ return ( ); }