diff --git a/frigate/api/event.py b/frigate/api/event.py index 7cdb933c9..17fc5c776 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -77,6 +77,8 @@ def events(): min_length = request.args.get("min_length", type=float) max_length = request.args.get("max_length", type=float) + sort = request.args.get("sort", type=str) + clauses = [] selected_columns = [ @@ -219,10 +221,22 @@ def events(): if len(clauses) == 0: clauses.append((True)) + if sort: + if sort == "score_asc": + order_by = Event.data["score"].asc() + elif sort == "score_desc": + order_by = Event.data["score"].desc() + elif sort == "date_asc": + Event.start_time.asc() + elif sort == "date_desc": + Event.start_time.desc() + else: + order_by = Event.start_time.desc() + events = ( Event.select(*selected_columns) .where(reduce(operator.and_, clauses)) - .order_by(Event.start_time.desc()) + .order_by(order_by) .limit(limit) .dicts() .iterator() diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index 2038aef42..a92399539 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -209,7 +209,7 @@ type CameraFilterButtonProps = { selectedCameras: string[] | undefined; updateCameraFilter: (cameras: string[] | undefined) => void; }; -function CamerasFilterButton({ +export function CamerasFilterButton({ allCameras, groups, selectedCameras, @@ -227,7 +227,7 @@ function CamerasFilterButton({ size="sm" > = 1 ? "text-selected-foreground" : "text-secondary-foreground"}`} />
- - {video.muted == false && ( - , - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - - - -)); -Slider.displayName = SliderPrimitive.Root.displayName; - -export { Slider }; diff --git a/web/src/components/ui/slider-volume.tsx b/web/src/components/ui/slider-volume.tsx deleted file mode 100644 index 1493488a3..000000000 --- a/web/src/components/ui/slider-volume.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from "react"; -import * as SliderPrimitive from "@radix-ui/react-slider"; - -import { cn } from "@/lib/utils"; - -const Slider = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - - - -)); -Slider.displayName = SliderPrimitive.Root.displayName; - -export { Slider }; diff --git a/web/src/components/ui/slider.tsx b/web/src/components/ui/slider.tsx index e161daec0..0f57209d8 100644 --- a/web/src/components/ui/slider.tsx +++ b/web/src/components/ui/slider.tsx @@ -1,7 +1,7 @@ -import * as React from "react" -import * as SliderPrimitive from "@radix-ui/react-slider" +import * as React from "react"; +import * as SliderPrimitive from "@radix-ui/react-slider"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Slider = React.forwardRef< React.ElementRef, @@ -11,7 +11,7 @@ const Slider = React.forwardRef< ref={ref} className={cn( "relative flex w-full touch-none select-none items-center", - className + className, )} {...props} > @@ -20,7 +20,68 @@ const Slider = React.forwardRef< -)) -Slider.displayName = SliderPrimitive.Root.displayName +)); +Slider.displayName = SliderPrimitive.Root.displayName; -export { Slider } +const VolumeSlider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)); +VolumeSlider.displayName = SliderPrimitive.Root.displayName; + +const NoThumbSlider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)); +NoThumbSlider.displayName = SliderPrimitive.Root.displayName; + +const DualThumbSlider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + + +)); +DualThumbSlider.displayName = SliderPrimitive.Root.displayName; + +export { DualThumbSlider, Slider, NoThumbSlider, VolumeSlider }; diff --git a/web/src/pages/SubmitPlus.tsx b/web/src/pages/SubmitPlus.tsx index 831b0370c..88c449362 100644 --- a/web/src/pages/SubmitPlus.tsx +++ b/web/src/pages/SubmitPlus.tsx @@ -1,5 +1,8 @@ import { baseUrl } from "@/api/baseUrl"; -import FilterCheckBox from "@/components/filter/FilterCheckBox"; +import { + CamerasFilterButton, + GeneralFilterContent, +} from "@/components/filter/ReviewFilterGroup"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -13,16 +16,25 @@ import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer"; import { DropdownMenu, DropdownMenuContent, - DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { DualThumbSlider } from "@/components/ui/slider"; import { Event } from "@/types/event"; import { FrigateConfig } from "@/types/frigateConfig"; import axios from "axios"; import { useCallback, useEffect, useMemo, useState } from "react"; import { isMobile } from "react-device-detect"; -import { FaList, FaVideo } from "react-icons/fa"; +import { + FaList, + FaSort, + FaSortAmountDown, + FaSortAmountUp, +} from "react-icons/fa"; +import { PiSlidersHorizontalFill } from "react-icons/pi"; import useSWR from "swr"; export default function SubmitPlus() { @@ -36,6 +48,11 @@ export default function SubmitPlus() { const [selectedCameras, setSelectedCameras] = useState(); const [selectedLabels, setSelectedLabels] = useState(); + const [scoreRange, setScoreRange] = useState(); + + // sort + + const [sort, setSort] = useState(); // data @@ -47,6 +64,9 @@ export default function SubmitPlus() { is_submitted: 0, cameras: selectedCameras ? selectedCameras.join(",") : null, labels: selectedLabels ? selectedLabels.join(",") : null, + min_score: scoreRange ? scoreRange[0] : null, + max_score: scoreRange ? scoreRange[1] : null, + sort: sort ? sort : null, }, ]); const [upload, setUpload] = useState(); @@ -104,12 +124,17 @@ export default function SubmitPlus() { return (
- +
+ + +
void; selectedLabels: string[] | undefined; + selectedScoreRange: number[] | undefined; + setSelectedCameras: (cameras: string[] | undefined) => void; setSelectedLabels: (cameras: string[] | undefined) => void; + setSelectedScoreRange: (range: number[] | undefined) => void; }; function PlusFilterGroup({ selectedCameras, - setSelectedCameras, selectedLabels, + selectedScoreRange, + setSelectedCameras, setSelectedLabels, + setSelectedScoreRange, }: PlusFilterGroupProps) { const { data: config } = useSWR("config"); @@ -217,97 +246,28 @@ function PlusFilterGroup({ return [...labels].sort(); }, [config, selectedCameras]); - const [open, setOpen] = useState<"none" | "camera" | "label">("none"); - const [currentCameras, setCurrentCameras] = useState( - undefined, + const [open, setOpen] = useState<"none" | "camera" | "label" | "score">( + "none", ); const [currentLabels, setCurrentLabels] = useState( undefined, ); + const [currentScoreRange, setCurrentScoreRange] = useState< + number[] | undefined + >(undefined); const Menu = isMobile ? Drawer : DropdownMenu; const Trigger = isMobile ? DrawerTrigger : DropdownMenuTrigger; const Content = isMobile ? DrawerContent : DropdownMenuContent; return ( -
- { - if (!open) { - setCurrentCameras(selectedCameras); - } - setOpen(open ? "camera" : "none"); - }} - > - - - - - - Filter Cameras - - - { - if (isChecked) { - setCurrentCameras(undefined); - } - }} - /> - -
- {allCameras.map((item) => ( - { - if (isChecked) { - const updatedCameras = currentCameras - ? [...currentCameras] - : []; - - updatedCameras.push(item); - setCurrentCameras(updatedCameras); - } else { - const updatedCameras = currentCameras - ? [...currentCameras] - : []; - - // can not deselect the last item - if (updatedCameras.length > 1) { - updatedCameras.splice(updatedCameras.indexOf(item), 1); - setCurrentCameras(updatedCameras); - } - } - }} - /> - ))} -
- -
- -
-
-
+
+ { @@ -318,8 +278,14 @@ function PlusFilterGroup({ }} > - - - Filter Labels - - - { - if (isChecked) { - setCurrentLabels(undefined); - } - }} + setOpen("none")} /> - -
- {allLabels.map((item) => ( - { - if (isChecked) { - const updatedLabels = currentLabels - ? [...currentLabels] - : []; - - updatedLabels.push(item); - setCurrentLabels(updatedLabels); - } else { - const updatedLabels = currentLabels - ? [...currentLabels] - : []; - - // can not deselect the last item - if (updatedLabels.length > 1) { - updatedLabels.splice(updatedLabels.indexOf(item), 1); - setCurrentLabels(updatedLabels); - } - } - }} - /> - ))} + +
+ { + setOpen(open ? "score" : "none"); + }} + > + + + + +
+ + setCurrentScoreRange([ + parseInt(e.target.value) / 100.0, + currentScoreRange?.at(1) ?? 1.0, + ]) + } + /> + + + setCurrentScoreRange([ + currentScoreRange?.at(0) ?? 0.5, + parseInt(e.target.value) / 100.0, + ]) + } + />
-
+
+ +
+ +
+
+ ); +} + +type PlusSortSelectorProps = { + selectedSort?: string; + setSelectedSort: (sort: string | undefined) => void; +}; +function PlusSortSelector({ + selectedSort, + setSelectedSort, +}: PlusSortSelectorProps) { + // menu state + + const [open, setOpen] = useState(false); + + // sort + + const [currentSort, setCurrentSort] = useState(); + const [currentDir, setCurrentDir] = useState("desc"); + + // components + + const Sort = selectedSort + ? selectedSort.split("_")[1] == "desc" + ? FaSortAmountDown + : FaSortAmountUp + : FaSort; + const Menu = isMobile ? Drawer : DropdownMenu; + const Trigger = isMobile ? DrawerTrigger : DropdownMenuTrigger; + const Content = isMobile ? DrawerContent : DropdownMenuContent; + + return ( +
+ { + setOpen(open); + + if (!open) { + const parts = selectedSort?.split("_"); + + if (parts?.length == 2) { + setCurrentSort(parts[0]); + setCurrentDir(parts[1]); + } + } + }} + > + + + + + setCurrentSort(value)} + > +
+ + + {currentSort == "date" ? ( + currentDir == "desc" ? ( + setCurrentDir("asc")} + /> + ) : ( + setCurrentDir("desc")} + /> + ) + ) : ( +
+ )} +
+
+ + + {currentSort == "score" ? ( + currentDir == "desc" ? ( + setCurrentDir("asc")} + /> + ) : ( + setCurrentDir("desc")} + /> + ) + ) : ( +
+ )} +
+ + +
+ +
diff --git a/web/src/views/events/RecordingView.tsx b/web/src/views/events/RecordingView.tsx index c4ac49f62..57608a693 100644 --- a/web/src/views/events/RecordingView.tsx +++ b/web/src/views/events/RecordingView.tsx @@ -366,7 +366,7 @@ export function RecordingView({ key={mainCamera} className={ isDesktop - ? `${mainCameraAspect == "tall" ? "xl:h-[90%]" : mainCameraAspect == "wide" ? "w-full" : "w-[78%]"} px-4 flex justify-center` + ? `${mainCameraAspect == "tall" ? "h-[50%] md:h-[60%] lg:h-[75%] xl:h-[90%]" : mainCameraAspect == "wide" ? "w-full" : "w-[78%]"} px-4 flex justify-center` : `portrait:w-full pt-2 ${mainCameraAspect == "wide" ? "landscape:w-full aspect-wide" : "landscape:h-[94%] aspect-video"}` } style={{