From 9bce6efddc7daf0013ca42af712eb3effaba0b0d Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 15 Oct 2024 15:55:05 -0600 Subject: [PATCH 1/2] Add cameras page to more filters --- .../components/filter/CamerasFilterButton.tsx | 100 +++++++++++------ .../components/filter/SearchFilterGroup.tsx | 63 +---------- .../overlay/dialog/PlatformAwareDialog.tsx | 4 +- .../overlay/dialog/SearchFilterDialog.tsx | 103 +++++++++++++++++- 4 files changed, 171 insertions(+), 99 deletions(-) diff --git a/web/src/components/filter/CamerasFilterButton.tsx b/web/src/components/filter/CamerasFilterButton.tsx index 563af8752..94f1a838e 100644 --- a/web/src/components/filter/CamerasFilterButton.tsx +++ b/web/src/components/filter/CamerasFilterButton.tsx @@ -69,6 +69,70 @@ export function CamerasFilterButton({ ); const content = ( + + ); + + if (isMobile) { + return ( + { + if (!open) { + setCurrentCameras(selectedCameras); + } + + setOpen(open); + }} + > + {trigger} + + {content} + + + ); + } + + return ( + { + if (!open) { + setCurrentCameras(selectedCameras); + } + setOpen(open); + }} + > + {trigger} + {content} + + ); +} + +type CamerasFilterContentProps = { + allCameras: string[]; + currentCameras: string[] | undefined; + groups: [string, CameraGroupConfig][]; + setCurrentCameras: (cameras: string[] | undefined) => void; + setOpen: (open: boolean) => void; + updateCameraFilter: (cameras: string[] | undefined) => void; +}; +export function CamerasFilterContent({ + allCameras, + currentCameras, + groups, + setCurrentCameras, + setOpen, + updateCameraFilter, +}: CamerasFilterContentProps) { + return ( <> {isMobile && ( <> @@ -158,40 +222,4 @@ export function CamerasFilterButton({ ); - - if (isMobile) { - return ( - { - if (!open) { - setCurrentCameras(selectedCameras); - } - - setOpen(open); - }} - > - {trigger} - - {content} - - - ); - } - - return ( - { - if (!open) { - setCurrentCameras(selectedCameras); - } - setOpen(open); - }} - > - {trigger} - {content} - - ); } diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index 30b554200..bbdac7513 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -10,7 +10,6 @@ import { Switch } from "../ui/switch"; import { Label } from "../ui/label"; import FilterSwitch from "./FilterSwitch"; import { FilterList } from "@/types/filter"; -import { CalendarRangeFilterButton } from "./CalendarFilterButton"; import { CamerasFilterButton } from "./CamerasFilterButton"; import { DEFAULT_SEARCH_FILTERS, @@ -80,8 +79,6 @@ export default function SearchFilterGroup({ return [...labels].sort(); }, [config, filterList, filter]); - const { data: allSubLabels } = useSWR(["sub_labels", { split_joined: 1 }]); - const allZones = useMemo(() => { if (filterList?.zones) { return filterList.zones; @@ -169,60 +166,12 @@ export default function SearchFilterGroup({ }} /> )} - - {filters.includes("date") && ( - - )} - {filters.includes("time") && ( - - onUpdateFilter({ ...filter, time_range }) - } - /> - )} - {filters.includes("zone") && allZones.length > 0 && ( - - onUpdateFilter({ ...filter, zones: newZones }) - } - /> - )} - {filters.includes("sub") && ( - - onUpdateFilter({ ...filter, sub_labels: newSubLabels }) - } - /> - )} - {config?.semantic_search?.enabled && - filters.includes("source") && - !filter?.search_type?.includes("similarity") && ( - - onUpdateFilter({ ...filter, search_type: newSearchSource }) - } - /> - )} + ); } diff --git a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx index f1c1834e3..a9aed8a65 100644 --- a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx +++ b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx @@ -65,9 +65,7 @@ export function PlatformAwareSheet({ if (isMobile) { return ( - + {content} diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx index 017ac1e88..27b172f14 100644 --- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx +++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx @@ -3,9 +3,37 @@ import { FaCog } from "react-icons/fa"; import { useState } from "react"; import { PlatformAwareSheet } from "./PlatformAwareDialog"; import { Button } from "@/components/ui/button"; +import { CamerasFilterContent } from "@/components/filter/CamerasFilterButton"; +import useSWR from "swr"; +import { SearchFilter, SearchSource } from "@/types/search"; +import { CameraGroupConfig } from "@/types/frigateConfig"; + +type SearchFilterDialogProps = { + filter?: SearchFilter; + filterValues: { + cameras: string[]; + labels: string[]; + zones: string[]; + search_type: SearchSource[]; + }; + groups: [string, CameraGroupConfig][]; + onUpdateFilter: (filter: SearchFilter) => void; +}; +export default function SearchFilterDialog({ + filter, + filterValues, + groups, + onUpdateFilter, +}: SearchFilterDialogProps) { + // data + + const { data: allSubLabels } = useSWR(["sub_labels", { split_joined: 1 }]); + const [currentCameras, setCurrentCameras] = useState( + filter?.cameras, + ); + + // state -type SearchFilterDialogProps = {}; -export default function SearchFilterDialog({}: SearchFilterDialogProps) { const [open, setOpen] = useState(false); const trigger = ( @@ -14,7 +42,20 @@ export default function SearchFilterDialog({}: SearchFilterDialogProps) { More Filters ); - const content = <>; + const content = ( + <> + { + onUpdateFilter({ ...filter, cameras: newCameras }); + }} + /> + + ); return ( ); } + +/** + * {filters.includes("date") && ( + + )} + {filters.includes("time") && ( + + onUpdateFilter({ ...filter, time_range }) + } + /> + )} + {filters.includes("zone") && allZones.length > 0 && ( + + onUpdateFilter({ ...filter, zones: newZones }) + } + /> + )} + {filters.includes("sub") && ( + + onUpdateFilter({ ...filter, sub_labels: newSubLabels }) + } + /> + )} + {config?.semantic_search?.enabled && + filters.includes("source") && + !filter?.search_type?.includes("similarity") && ( + + onUpdateFilter({ ...filter, search_type: newSearchSource }) + } + /> + )} + */ From af4a291cb2a3c9adfa851660a9f7297dd801a057 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 15 Oct 2024 16:09:51 -0600 Subject: [PATCH 2/2] Add time range to side filter --- .../components/filter/SearchFilterGroup.tsx | 194 ++---------------- .../overlay/dialog/SearchFilterDialog.tsx | 176 ++++++++++++++-- 2 files changed, 175 insertions(+), 195 deletions(-) diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index bbdac7513..376c9b599 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -29,6 +29,7 @@ import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog"; import { FaArrowRight, FaClock } from "react-icons/fa"; import { useFormattedHour } from "@/hooks/use-date-utils"; import SearchFilterDialog from "../overlay/dialog/SearchFilterDialog"; +import { CalendarRangeFilterButton } from "./CalendarFilterButton"; type SearchFilterGroupProps = { className: string; @@ -166,7 +167,22 @@ export default function SearchFilterGroup({ }} /> )} + {filters.includes("date") && ( + + )} void; -}; -function TimeRangeFilterButton({ - config, - timeRange, - updateTimeRange, -}: TimeRangeFilterButtonProps) { - const [open, setOpen] = useState(false); - const [startOpen, setStartOpen] = useState(false); - const [endOpen, setEndOpen] = useState(false); - - const [afterHour, beforeHour] = useMemo(() => { - if (!timeRange || !timeRange.includes(",")) { - return [DEFAULT_TIME_RANGE_AFTER, DEFAULT_TIME_RANGE_BEFORE]; - } - - return timeRange.split(","); - }, [timeRange]); - - const [selectedAfterHour, setSelectedAfterHour] = useState(afterHour); - const [selectedBeforeHour, setSelectedBeforeHour] = useState(beforeHour); - - // format based on locale - - const formattedAfter = useFormattedHour(config, afterHour); - const formattedBefore = useFormattedHour(config, beforeHour); - const formattedSelectedAfter = useFormattedHour(config, selectedAfterHour); - const formattedSelectedBefore = useFormattedHour(config, selectedBeforeHour); - - useEffect(() => { - setSelectedAfterHour(afterHour); - setSelectedBeforeHour(beforeHour); - // only refresh when state changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [timeRange]); - - const trigger = ( - - ); - const content = ( -
-
- { - if (!open) { - setStartOpen(false); - } - }} - > - - - - - { - const clock = e.target.value; - const [hour, minute, _] = clock.split(":"); - setSelectedAfterHour(`${hour}:${minute}`); - }} - /> - - - - { - if (!open) { - setEndOpen(false); - } - }} - > - - - - - { - const clock = e.target.value; - const [hour, minute, _] = clock.split(":"); - setSelectedBeforeHour(`${hour}:${minute}`); - }} - /> - - -
- -
- - -
-
- ); - - return ( - { - setOpen(open); - }} - /> - ); -} - type ZoneFilterButtonProps = { allZones: string[]; selectedZones?: string[]; diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx index 27b172f14..17ed0cb30 100644 --- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx +++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx @@ -1,14 +1,27 @@ -import { FaCog } from "react-icons/fa"; +import { FaArrowRight, FaCog } from "react-icons/fa"; -import { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { PlatformAwareSheet } from "./PlatformAwareDialog"; import { Button } from "@/components/ui/button"; -import { CamerasFilterContent } from "@/components/filter/CamerasFilterButton"; import useSWR from "swr"; -import { SearchFilter, SearchSource } from "@/types/search"; -import { CameraGroupConfig } from "@/types/frigateConfig"; +import { + DEFAULT_TIME_RANGE_AFTER, + DEFAULT_TIME_RANGE_BEFORE, + SearchFilter, + SearchSource, +} from "@/types/search"; +import { CameraGroupConfig, FrigateConfig } from "@/types/frigateConfig"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { isDesktop } from "react-device-detect"; +import { useFormattedHour } from "@/hooks/use-date-utils"; +import Heading from "@/components/ui/heading"; type SearchFilterDialogProps = { + config?: FrigateConfig; filter?: SearchFilter; filterValues: { cameras: string[]; @@ -20,6 +33,7 @@ type SearchFilterDialogProps = { onUpdateFilter: (filter: SearchFilter) => void; }; export default function SearchFilterDialog({ + config, filter, filterValues, groups, @@ -27,10 +41,8 @@ export default function SearchFilterDialog({ }: SearchFilterDialogProps) { // data + const [currentFilter, setCurrentFilter] = useState(filter ?? {}); const { data: allSubLabels } = useSWR(["sub_labels", { split_joined: 1 }]); - const [currentCameras, setCurrentCameras] = useState( - filter?.cameras, - ); // state @@ -44,15 +56,12 @@ export default function SearchFilterDialog({ ); const content = ( <> - { - onUpdateFilter({ ...filter, cameras: newCameras }); - }} + + setCurrentFilter({ time_range: newRange, ...currentFilter }) + } /> ); @@ -68,6 +77,139 @@ export default function SearchFilterDialog({ ); } +type TimeRangeFilterContentProps = { + config?: FrigateConfig; + timeRange?: string; + updateTimeRange: (range: string | undefined) => void; +}; +function TimeRangeFilterContent({ + config, + timeRange, + updateTimeRange, +}: TimeRangeFilterContentProps) { + const [startOpen, setStartOpen] = useState(false); + const [endOpen, setEndOpen] = useState(false); + + const [afterHour, beforeHour] = useMemo(() => { + if (!timeRange || !timeRange.includes(",")) { + return [DEFAULT_TIME_RANGE_AFTER, DEFAULT_TIME_RANGE_BEFORE]; + } + + return timeRange.split(","); + }, [timeRange]); + + const [selectedAfterHour, setSelectedAfterHour] = useState(afterHour); + const [selectedBeforeHour, setSelectedBeforeHour] = useState(beforeHour); + + // format based on locale + + const formattedSelectedAfter = useFormattedHour(config, selectedAfterHour); + const formattedSelectedBefore = useFormattedHour(config, selectedBeforeHour); + + useEffect(() => { + setSelectedAfterHour(afterHour); + setSelectedBeforeHour(beforeHour); + // only refresh when state changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [timeRange]); + + useEffect(() => { + if ( + selectedAfterHour == DEFAULT_TIME_RANGE_AFTER && + selectedBeforeHour == DEFAULT_TIME_RANGE_BEFORE + ) { + updateTimeRange(undefined); + } else { + updateTimeRange(`${selectedAfterHour},${selectedBeforeHour}`); + } + // only refresh when state changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedAfterHour, selectedBeforeHour]); + + return ( +
+ Time Range +
+ { + if (!open) { + setStartOpen(false); + } + }} + > + + + + + { + const clock = e.target.value; + const [hour, minute, _] = clock.split(":"); + setSelectedAfterHour(`${hour}:${minute}`); + }} + /> + + + + { + if (!open) { + setEndOpen(false); + } + }} + > + + + + + { + const clock = e.target.value; + const [hour, minute, _] = clock.split(":"); + setSelectedBeforeHour(`${hour}:${minute}`); + }} + /> + + +
+
+ ); +} + /** * {filters.includes("date") && (