add frontend speed filter

This commit is contained in:
Josh Hawkins 2024-12-23 10:05:18 -06:00
parent adaacd20a1
commit fe231e2391
8 changed files with 162 additions and 2 deletions

View File

@ -25,6 +25,8 @@ class EventsQueryParams(BaseModel):
favorites: Optional[int] = None
min_score: Optional[float] = None
max_score: Optional[float] = None
min_speed: Optional[float] = None
max_speed: Optional[float] = None
is_submitted: Optional[int] = None
min_length: Optional[float] = None
max_length: Optional[float] = None
@ -51,6 +53,8 @@ class EventsSearchQueryParams(BaseModel):
timezone: Optional[str] = "utc"
min_score: Optional[float] = None
max_score: Optional[float] = None
min_speed: Optional[float] = None
max_speed: Optional[float] = None
sort: Optional[str] = None

View File

@ -92,6 +92,8 @@ def events(params: EventsQueryParams = Depends()):
favorites = params.favorites
min_score = params.min_score
max_score = params.max_score
min_speed = params.min_speed
max_speed = params.max_speed
is_submitted = params.is_submitted
min_length = params.min_length
max_length = params.max_length
@ -226,6 +228,12 @@ def events(params: EventsQueryParams = Depends()):
if min_score is not None:
clauses.append((Event.data["score"] >= min_score))
if max_speed is not None:
clauses.append((Event.data["average_estimated_speed"] <= max_speed))
if min_speed is not None:
clauses.append((Event.data["average_estimated_speed"] >= min_speed))
if min_length is not None:
clauses.append(((Event.end_time - Event.start_time) >= min_length))
@ -249,6 +257,10 @@ def events(params: EventsQueryParams = Depends()):
order_by = Event.data["score"].asc()
elif sort == "score_desc":
order_by = Event.data["score"].desc()
elif sort == "speed_asc":
order_by = Event.data["average_estimated_speed"].asc()
elif sort == "speed_desc":
order_by = Event.data["average_estimated_speed"].desc()
elif sort == "date_asc":
order_by = Event.start_time.asc()
elif sort == "date_desc":
@ -375,6 +387,8 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
before = params.before
min_score = params.min_score
max_score = params.max_score
min_speed = params.min_speed
max_speed = params.max_speed
time_range = params.time_range
has_clip = params.has_clip
has_snapshot = params.has_snapshot
@ -474,6 +488,16 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
if max_score is not None:
event_filters.append((Event.data["score"] <= max_score))
if min_speed is not None and max_speed is not None:
event_filters.append(
(Event.data["average_estimated_speed"].between(min_speed, max_speed))
)
else:
if min_speed is not None:
event_filters.append((Event.data["average_estimated_speed"] >= min_speed))
if max_speed is not None:
event_filters.append((Event.data["average_estimated_speed"] <= max_speed))
if time_range != DEFAULT_TIME_RANGE:
tz_name = params.timezone
hour_modifier, minute_modifier, _ = get_tz_modifiers(tz_name)
@ -613,6 +637,10 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
processed_events.sort(key=lambda x: x["score"])
elif min_score is not None and max_score is not None and sort == "score_desc":
processed_events.sort(key=lambda x: x["score"], reverse=True)
elif min_speed is not None and max_speed is not None and sort == "speed_asc":
processed_events.sort(key=lambda x: x["average_estimated_speed"])
elif min_speed is not None and max_speed is not None and sort == "speed_desc":
processed_events.sort(key=lambda x: x["average_estimated_speed"], reverse=True)
elif sort == "date_asc":
processed_events.sort(key=lambda x: x["start_time"])
else:

View File

@ -114,6 +114,9 @@ export default function SearchFilterGroup({
if (filter?.min_score || filter?.max_score) {
sortTypes.push("score_desc", "score_asc");
}
if (filter?.min_speed || filter?.max_speed) {
sortTypes.push("speed_desc", "speed_asc");
}
if (filter?.event_id || filter?.query) {
sortTypes.push("relevance");
}
@ -496,6 +499,8 @@ export function SortTypeContent({
date_desc: "Date (Descending)",
score_asc: "Object Score (Ascending)",
score_desc: "Object Score (Descending)",
speed_asc: "Estimated Speed (Ascending)",
speed_desc: "Estimated Speed (Descending)",
relevance: "Relevance",
};

View File

@ -216,11 +216,14 @@ export default function InputWithTags({
type == "after" ||
type == "time_range" ||
type == "min_score" ||
type == "max_score"
type == "max_score" ||
type == "min_speed" ||
type == "max_speed"
) {
const newFilters = { ...filters };
let timestamp = 0;
let score = 0;
let speed = 0;
switch (type) {
case "before":
@ -294,6 +297,40 @@ export default function InputWithTags({
newFilters[type] = score / 100;
}
break;
case "min_speed":
case "max_speed":
speed = parseFloat(value);
if (score >= 0) {
// Check for conflicts between min_speed and max_speed
if (
type === "min_speed" &&
filters.max_speed !== undefined &&
speed > filters.max_speed
) {
toast.error(
"The 'min_speed' must be less than or equal to the 'max_speed'.",
{
position: "top-center",
},
);
return;
}
if (
type === "max_speed" &&
filters.min_speed !== undefined &&
speed < filters.min_speed
) {
toast.error(
"The 'max_speed' must be greater than or equal to the 'min_speed'.",
{
position: "top-center",
},
);
return;
}
newFilters[type] = speed;
}
break;
case "time_range":
newFilters[type] = value;
break;
@ -369,6 +406,10 @@ export default function InputWithTags({
}`;
} else if (filterType === "min_score" || filterType === "max_score") {
return Math.round(Number(filterValues) * 100).toString() + "%";
} else if (filterType === "min_speed" || filterType === "max_speed") {
return (
filterValues + (config?.ui.unit_system == "metric" ? " kph" : " mph")
);
} else if (
filterType === "has_clip" ||
filterType === "has_snapshot" ||
@ -397,7 +438,11 @@ export default function InputWithTags({
((filterType === "min_score" || filterType === "max_score") &&
!isNaN(Number(trimmedValue)) &&
Number(trimmedValue) >= 50 &&
Number(trimmedValue) <= 100)
Number(trimmedValue) <= 100) ||
((filterType === "min_speed" || filterType === "max_speed") &&
!isNaN(Number(trimmedValue)) &&
Number(trimmedValue) >= 1 &&
Number(trimmedValue) <= 150)
) {
createFilter(
filterType,

View File

@ -71,9 +71,11 @@ export default function SearchFilterDialog({
currentFilter &&
(currentFilter.time_range ||
(currentFilter.min_score ?? 0) > 0.5 ||
(currentFilter.min_speed ?? 1) > 1 ||
(currentFilter.has_snapshot ?? 0) === 1 ||
(currentFilter.has_clip ?? 0) === 1 ||
(currentFilter.max_score ?? 1) < 1 ||
(currentFilter.max_speed ?? 150) < 150 ||
(currentFilter.zones?.length ?? 0) > 0 ||
(currentFilter.sub_labels?.length ?? 0) > 0),
[currentFilter],
@ -124,6 +126,14 @@ export default function SearchFilterDialog({
setCurrentFilter({ ...currentFilter, min_score: min, max_score: max })
}
/>
<SpeedFilterContent
config={config}
minSpeed={currentFilter.min_speed}
maxSpeed={currentFilter.max_speed}
setSpeedRange={(min, max) =>
setCurrentFilter({ ...currentFilter, min_speed: min, max_speed: max })
}
/>
<SnapshotClipFilterContent
config={config}
hasSnapshot={
@ -178,6 +188,8 @@ export default function SearchFilterDialog({
search_type: undefined,
min_score: undefined,
max_score: undefined,
min_speed: undefined,
max_speed: undefined,
has_snapshot: undefined,
has_clip: undefined,
}));
@ -521,6 +533,62 @@ export function ScoreFilterContent({
);
}
type SpeedFilterContentProps = {
config?: FrigateConfig;
minSpeed: number | undefined;
maxSpeed: number | undefined;
setSpeedRange: (min: number | undefined, max: number | undefined) => void;
};
export function SpeedFilterContent({
config,
minSpeed,
maxSpeed,
setSpeedRange,
}: SpeedFilterContentProps) {
return (
<div className="overflow-x-hidden">
<DropdownMenuSeparator className="mb-3" />
<div className="mb-3 text-lg">
Estimated Speed ({config?.ui.unit_system == "metric" ? "kph" : "mph"})
</div>
<div className="flex items-center gap-1">
<Input
className="w-14 text-center"
inputMode="numeric"
value={minSpeed ?? 1}
onChange={(e) => {
const value = e.target.value;
if (value) {
setSpeedRange(parseInt(value), maxSpeed ?? 1.0);
}
}}
/>
<DualThumbSlider
className="mx-2 w-full"
min={1}
max={150}
step={1}
value={[minSpeed ?? 1, maxSpeed ?? 150]}
onValueChange={([min, max]) => setSpeedRange(min, max)}
/>
<Input
className="w-14 text-center"
inputMode="numeric"
value={maxSpeed ?? 150}
onChange={(e) => {
const value = e.target.value;
if (value) {
setSpeedRange(minSpeed ?? 1, parseInt(value));
}
}}
/>
</div>
</div>
);
}
type SnapshotClipContentProps = {
config?: FrigateConfig;
hasSnapshot: boolean | undefined;

View File

@ -112,6 +112,8 @@ export default function Explore() {
search_type: searchSearchParams["search_type"],
min_score: searchSearchParams["min_score"],
max_score: searchSearchParams["max_score"],
min_speed: searchSearchParams["min_speed"],
max_speed: searchSearchParams["max_speed"],
has_snapshot: searchSearchParams["has_snapshot"],
is_submitted: searchSearchParams["is_submitted"],
has_clip: searchSearchParams["has_clip"],
@ -145,6 +147,8 @@ export default function Explore() {
search_type: searchSearchParams["search_type"],
min_score: searchSearchParams["min_score"],
max_score: searchSearchParams["max_score"],
min_speed: searchSearchParams["min_speed"],
max_speed: searchSearchParams["max_speed"],
has_snapshot: searchSearchParams["has_snapshot"],
is_submitted: searchSearchParams["is_submitted"],
has_clip: searchSearchParams["has_clip"],

View File

@ -70,6 +70,8 @@ export type SearchFilter = {
after?: number;
min_score?: number;
max_score?: number;
min_speed?: number;
max_speed?: number;
has_snapshot?: number;
has_clip?: number;
is_submitted?: number;
@ -91,6 +93,8 @@ export type SearchQueryParams = {
after?: string;
min_score?: number;
max_score?: number;
min_speed?: number;
max_speed?: number;
search_type?: string;
limit?: number;
in_progress?: number;

View File

@ -158,6 +158,8 @@ export default function SearchView({
after: [formatDateToLocaleString(-5)],
min_score: ["50"],
max_score: ["100"],
min_speed: ["1"],
max_speed: ["150"],
has_clip: ["yes", "no"],
has_snapshot: ["yes", "no"],
...(config?.plus?.enabled &&