diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx index ed091b350..f067b92af 100644 --- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx +++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx @@ -25,6 +25,8 @@ import { DropdownMenuSeparator } from "@/components/ui/dropdown-menu"; import { cn } from "@/lib/utils"; import { DualThumbSlider } from "@/components/ui/slider"; import { Input } from "@/components/ui/input"; +import { Checkbox } from "@/components/ui/checkbox"; +import ToggleButton from "@/components/ui/toggle-button"; type SearchFilterDialogProps = { config?: FrigateConfig; @@ -63,6 +65,8 @@ export default function SearchFilterDialog({ currentFilter && (currentFilter.time_range || (currentFilter.min_score ?? 0) > 0.5 || + (currentFilter.has_snapshot ?? 0) === 1 || + (currentFilter.has_clip ?? 0) === 1 || (currentFilter.max_score ?? 1) < 1 || (currentFilter.zones?.length ?? 0) > 0 || (currentFilter.sub_labels?.length ?? 0) > 0), @@ -113,6 +117,26 @@ export default function SearchFilterDialog({ setCurrentFilter({ ...currentFilter, min_score: min, max_score: max }) } /> + + setCurrentFilter({ + ...currentFilter, + has_snapshot: + snapshot !== undefined ? (snapshot ? 1 : 0) : undefined, + has_clip: clip !== undefined ? (clip ? 1 : 0) : undefined, + }) + } + /> {isDesktop && }
); } + +type SnapshotClipContentProps = { + hasSnapshot: boolean | undefined; + hasClip: boolean | undefined; + setSnapshotClip: ( + snapshot: boolean | undefined, + clip: boolean | undefined, + ) => void; +}; + +function SnapshotClipFilterContent({ + hasSnapshot, + hasClip, + setSnapshotClip, +}: SnapshotClipContentProps) { + const [isSnapshotFilterActive, setIsSnapshotFilterActive] = useState( + hasSnapshot !== undefined, + ); + const [isClipFilterActive, setIsClipFilterActive] = useState( + hasClip !== undefined, + ); + + useEffect(() => { + setIsSnapshotFilterActive(hasSnapshot !== undefined); + }, [hasSnapshot]); + + useEffect(() => { + setIsClipFilterActive(hasClip !== undefined); + }, [hasClip]); + + return ( +
+ +
Features
+ +
+
+
+ { + setIsSnapshotFilterActive(checked as boolean); + if (checked) { + setSnapshotClip(true, hasClip); + } + if (!checked) setSnapshotClip(undefined, hasClip); + }} + /> + +
+
+ setSnapshotClip(true, hasClip)} + disabled={!isSnapshotFilterActive} + > + Yes + + setSnapshotClip(false, hasClip)} + disabled={!isSnapshotFilterActive} + > + No + +
+
+ +
+
+ { + setIsClipFilterActive(checked as boolean); + if (checked) { + setSnapshotClip(hasSnapshot, true); + } + if (!checked) setSnapshotClip(hasSnapshot, undefined); + }} + /> + +
+
+ setSnapshotClip(hasSnapshot, true)} + disabled={!isClipFilterActive} + > + Yes + + setSnapshotClip(hasSnapshot, false)} + disabled={!isClipFilterActive} + > + No + +
+
+
+
+ ); +} diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 202b079a6..eee9fce9b 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -111,6 +111,8 @@ export default function Explore() { search_type: searchSearchParams["search_type"], min_score: searchSearchParams["min_score"], max_score: searchSearchParams["max_score"], + has_snapshot: searchSearchParams["has_snapshot"], + has_clip: searchSearchParams["has_clip"], limit: Object.keys(searchSearchParams).length == 0 ? API_LIMIT : undefined, timezone, @@ -139,6 +141,8 @@ export default function Explore() { search_type: searchSearchParams["search_type"], min_score: searchSearchParams["min_score"], max_score: searchSearchParams["max_score"], + has_snapshot: searchSearchParams["has_snapshot"], + has_clip: searchSearchParams["has_clip"], event_id: searchSearchParams["event_id"], timezone, include_thumbnails: 0, diff --git a/web/src/types/search.ts b/web/src/types/search.ts index d7f3fb97d..5421fe927 100644 --- a/web/src/types/search.ts +++ b/web/src/types/search.ts @@ -59,6 +59,8 @@ export type SearchFilter = { after?: number; min_score?: number; max_score?: number; + has_snapshot?: number; + has_clip?: number; time_range?: string; search_type?: SearchSource[]; event_id?: string;