mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 13:45:25 +03:00
Implement ability to sort frigate plus submissions
This commit is contained in:
parent
4870f976d4
commit
5700ff2019
@ -77,6 +77,8 @@ def events():
|
|||||||
min_length = request.args.get("min_length", type=float)
|
min_length = request.args.get("min_length", type=float)
|
||||||
max_length = request.args.get("max_length", type=float)
|
max_length = request.args.get("max_length", type=float)
|
||||||
|
|
||||||
|
sort = request.args.get("sort", type=str)
|
||||||
|
|
||||||
clauses = []
|
clauses = []
|
||||||
|
|
||||||
selected_columns = [
|
selected_columns = [
|
||||||
@ -219,10 +221,22 @@ def events():
|
|||||||
if len(clauses) == 0:
|
if len(clauses) == 0:
|
||||||
clauses.append((True))
|
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 = (
|
events = (
|
||||||
Event.select(*selected_columns)
|
Event.select(*selected_columns)
|
||||||
.where(reduce(operator.and_, clauses))
|
.where(reduce(operator.and_, clauses))
|
||||||
.order_by(Event.start_time.desc())
|
.order_by(order_by)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.dicts()
|
.dicts()
|
||||||
.iterator()
|
.iterator()
|
||||||
|
|||||||
@ -20,13 +20,20 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
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 { DualThumbSlider } from "@/components/ui/slider";
|
||||||
import { Event } from "@/types/event";
|
import { Event } from "@/types/event";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import { FaList } from "react-icons/fa";
|
import {
|
||||||
|
FaList,
|
||||||
|
FaSort,
|
||||||
|
FaSortAmountDown,
|
||||||
|
FaSortAmountUp,
|
||||||
|
} from "react-icons/fa";
|
||||||
import { PiSlidersHorizontalFill } from "react-icons/pi";
|
import { PiSlidersHorizontalFill } from "react-icons/pi";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
@ -43,6 +50,10 @@ export default function SubmitPlus() {
|
|||||||
const [selectedLabels, setSelectedLabels] = useState<string[]>();
|
const [selectedLabels, setSelectedLabels] = useState<string[]>();
|
||||||
const [scoreRange, setScoreRange] = useState<number[]>();
|
const [scoreRange, setScoreRange] = useState<number[]>();
|
||||||
|
|
||||||
|
// sort
|
||||||
|
|
||||||
|
const [sort, setSort] = useState<string>();
|
||||||
|
|
||||||
// data
|
// data
|
||||||
|
|
||||||
const { data: events, mutate: refresh } = useSWR<Event[]>([
|
const { data: events, mutate: refresh } = useSWR<Event[]>([
|
||||||
@ -55,6 +66,7 @@ export default function SubmitPlus() {
|
|||||||
labels: selectedLabels ? selectedLabels.join(",") : null,
|
labels: selectedLabels ? selectedLabels.join(",") : null,
|
||||||
min_score: scoreRange ? scoreRange[0] : null,
|
min_score: scoreRange ? scoreRange[0] : null,
|
||||||
max_score: scoreRange ? scoreRange[1] : null,
|
max_score: scoreRange ? scoreRange[1] : null,
|
||||||
|
sort: sort ? sort : null,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const [upload, setUpload] = useState<Event>();
|
const [upload, setUpload] = useState<Event>();
|
||||||
@ -112,14 +124,17 @@ export default function SubmitPlus() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="size-full flex flex-col">
|
<div className="size-full flex flex-col">
|
||||||
<PlusFilterGroup
|
<div className="w-full h-16 px-2 flex items-center justify-between overflow-x-auto">
|
||||||
selectedCameras={selectedCameras}
|
<PlusFilterGroup
|
||||||
selectedLabels={selectedLabels}
|
selectedCameras={selectedCameras}
|
||||||
selectedScoreRange={scoreRange}
|
selectedLabels={selectedLabels}
|
||||||
setSelectedCameras={setSelectedCameras}
|
selectedScoreRange={scoreRange}
|
||||||
setSelectedLabels={setSelectedLabels}
|
setSelectedCameras={setSelectedCameras}
|
||||||
setSelectedScoreRange={setScoreRange}
|
setSelectedLabels={setSelectedLabels}
|
||||||
/>
|
setSelectedScoreRange={setScoreRange}
|
||||||
|
/>
|
||||||
|
<PlusSortSelector selectedSort={sort} setSelectedSort={setSort} />
|
||||||
|
</div>
|
||||||
<div className="size-full flex flex-1 flex-wrap content-start gap-2 md:gap-4 overflow-y-auto no-scrollbar">
|
<div className="size-full flex flex-1 flex-wrap content-start gap-2 md:gap-4 overflow-y-auto no-scrollbar">
|
||||||
<div className="w-full p-2 grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2">
|
<div className="w-full p-2 grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2">
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -246,7 +261,7 @@ function PlusFilterGroup({
|
|||||||
const Content = isMobile ? DrawerContent : DropdownMenuContent;
|
const Content = isMobile ? DrawerContent : DropdownMenuContent;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-16 mx-2 flex justify-start gap-2 items-center">
|
<div className="h-full flex justify-start gap-2 items-center">
|
||||||
<CamerasFilterButton
|
<CamerasFilterButton
|
||||||
allCameras={allCameras}
|
allCameras={allCameras}
|
||||||
groups={[]}
|
groups={[]}
|
||||||
@ -371,3 +386,161 @@ function PlusFilterGroup({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<string>();
|
||||||
|
const [currentDir, setCurrentDir] = useState<string>("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 (
|
||||||
|
<div className="h-full flex justify-start gap-2 items-center">
|
||||||
|
<Menu
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setOpen(open);
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
const parts = selectedSort?.split("_");
|
||||||
|
|
||||||
|
if (parts?.length == 2) {
|
||||||
|
setCurrentSort(parts[0]);
|
||||||
|
setCurrentDir(parts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trigger asChild>
|
||||||
|
<Button
|
||||||
|
className="flex items-center gap-2 capitalize"
|
||||||
|
size="sm"
|
||||||
|
variant={selectedSort == undefined ? "default" : "select"}
|
||||||
|
>
|
||||||
|
<Sort
|
||||||
|
className={`${selectedSort == undefined ? "text-secondary-foreground" : "text-selected-foreground"}`}
|
||||||
|
/>
|
||||||
|
<div className="hidden md:block text-primary">
|
||||||
|
{selectedSort == undefined ? "Sort" : selectedSort.split("_")[0]}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Trigger>
|
||||||
|
<Content
|
||||||
|
className={`p-2 flex flex-col justify-center gap-2 ${isMobile ? "max-h-[75dvh]" : ""}`}
|
||||||
|
>
|
||||||
|
<RadioGroup
|
||||||
|
className={`flex flex-col gap-4 ${isMobile ? "mt-4" : ""}`}
|
||||||
|
onValueChange={(value) => setCurrentSort(value)}
|
||||||
|
>
|
||||||
|
<div className="w-full flex items-center gap-2">
|
||||||
|
<RadioGroupItem
|
||||||
|
className={
|
||||||
|
currentSort == "date"
|
||||||
|
? "from-selected/50 to-selected/90 text-selected bg-selected"
|
||||||
|
: "from-secondary/50 to-secondary/90 text-secondary bg-secondary"
|
||||||
|
}
|
||||||
|
id="date"
|
||||||
|
value="date"
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
className="w-full cursor-pointer capitalize"
|
||||||
|
htmlFor="date"
|
||||||
|
>
|
||||||
|
Date
|
||||||
|
</Label>
|
||||||
|
{currentSort == "date" ? (
|
||||||
|
currentDir == "desc" ? (
|
||||||
|
<FaSortAmountDown
|
||||||
|
className="size-5 cursor-pointer"
|
||||||
|
onClick={() => setCurrentDir("asc")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FaSortAmountUp
|
||||||
|
className="size-5 cursor-pointer"
|
||||||
|
onClick={() => setCurrentDir("desc")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="size-5" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex items-center gap-2">
|
||||||
|
<RadioGroupItem
|
||||||
|
className={
|
||||||
|
currentSort == "score"
|
||||||
|
? "from-selected/50 to-selected/90 text-selected bg-selected"
|
||||||
|
: "from-secondary/50 to-secondary/90 text-secondary bg-secondary"
|
||||||
|
}
|
||||||
|
id="score"
|
||||||
|
value="score"
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
className="w-full cursor-pointer capitalize"
|
||||||
|
htmlFor="score"
|
||||||
|
>
|
||||||
|
Score
|
||||||
|
</Label>
|
||||||
|
{currentSort == "score" ? (
|
||||||
|
currentDir == "desc" ? (
|
||||||
|
<FaSortAmountDown
|
||||||
|
className="size-5 cursor-pointer"
|
||||||
|
onClick={() => setCurrentDir("asc")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FaSortAmountUp
|
||||||
|
className="size-5 cursor-pointer"
|
||||||
|
onClick={() => setCurrentDir("desc")}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="size-5" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<div className="p-2 flex justify-evenly items-center">
|
||||||
|
<Button
|
||||||
|
variant="select"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedSort(`${currentSort}_${currentDir}`);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Apply
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentSort(undefined);
|
||||||
|
setCurrentDir("desc");
|
||||||
|
setSelectedSort(undefined);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user