forked from Github/frigate
Refactor Search Page (#13645)
* Always enable search page * Always show eents when searching * No default search background * Center and show all filters when semantic search is not enabled * Limit number of default items shown * Adjust search options * Add support for sub label filtering * Separate out filters and clean up detail pane * Tablet cleanup * Fix current hour search preview * Handle single lists * Cleanup api search
This commit is contained in:
@@ -11,19 +11,13 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { Preview } from "@/types/preview";
|
||||
import { SearchFilter, SearchResult } from "@/types/search";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import {
|
||||
LuExternalLink,
|
||||
LuImage,
|
||||
LuSearchCheck,
|
||||
LuSearchX,
|
||||
LuText,
|
||||
LuXCircle,
|
||||
} from "react-icons/lu";
|
||||
import { Link } from "react-router-dom";
|
||||
import { LuImage, LuSearchX, LuText, LuXCircle } from "react-icons/lu";
|
||||
import useSWR from "swr";
|
||||
|
||||
type SearchViewProps = {
|
||||
search: string;
|
||||
@@ -51,6 +45,10 @@ export default function SearchView({
|
||||
onUpdateFilter,
|
||||
onOpenSearch,
|
||||
}: SearchViewProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config", {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
// remove duplicate event ids
|
||||
|
||||
const uniqueResults = useMemo(() => {
|
||||
@@ -96,6 +94,11 @@ export default function SearchView({
|
||||
return Math.round(confidence * 100);
|
||||
};
|
||||
|
||||
const hasExistingSearch = useMemo(
|
||||
() => searchResults != undefined || searchFilter != undefined,
|
||||
[searchResults, searchFilter],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col pt-2 md:py-2">
|
||||
<Toaster closeButton={true} />
|
||||
@@ -107,52 +110,49 @@ export default function SearchView({
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="relative mb-2 flex h-11 items-center justify-between pl-2 pr-2 md:pl-3">
|
||||
<div className="relative mr-3 w-full md:w-1/3">
|
||||
<Input
|
||||
className="text-md w-full bg-muted pr-10"
|
||||
placeholder={
|
||||
isMobileOnly ? "Search" : "Search for a detected object..."
|
||||
}
|
||||
value={similaritySearch ? "" : search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
{search && (
|
||||
<LuXCircle
|
||||
className="absolute right-2 top-1/2 h-5 w-5 -translate-y-1/2 cursor-pointer text-muted-foreground hover:text-primary"
|
||||
onClick={() => setSearch("")}
|
||||
<div
|
||||
className={cn(
|
||||
"relative mb-2 flex h-11 items-center pl-2 pr-2 md:pl-3",
|
||||
config?.semantic_search?.enabled
|
||||
? "justify-between"
|
||||
: "justify-center",
|
||||
isMobileOnly && "h-[88px] flex-wrap gap-2",
|
||||
)}
|
||||
>
|
||||
{config?.semantic_search?.enabled && (
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full",
|
||||
hasExistingSearch ? "mr-3 md:w-1/3" : "md:ml-[25%] md:w-1/2",
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
className="text-md w-full bg-muted pr-10"
|
||||
placeholder={
|
||||
isMobileOnly ? "Search" : "Search for a detected object..."
|
||||
}
|
||||
value={similaritySearch ? "" : search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SearchFilterGroup
|
||||
filter={searchFilter}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
|
||||
{searchTerm.length == 0 && (
|
||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
|
||||
<LuSearchCheck className="size-16" />
|
||||
Search
|
||||
<div className="mt-2 max-w-64 text-sm text-secondary-foreground">
|
||||
Frigate can find detected objects in your review items.
|
||||
</div>
|
||||
<div className="mt-2 flex items-center text-center text-sm text-primary">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/semantic_search"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
Read the Documentation{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
{search && (
|
||||
<LuXCircle
|
||||
className="absolute right-2 top-1/2 h-5 w-5 -translate-y-1/2 cursor-pointer text-muted-foreground hover:text-primary"
|
||||
onClick={() => setSearch("")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasExistingSearch && (
|
||||
<SearchFilterGroup
|
||||
className={cn("", isMobileOnly && "w-full justify-between")}
|
||||
filter={searchFilter}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
|
||||
{searchTerm.length > 0 && searchResults?.length == 0 && (
|
||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
|
||||
<LuSearchX className="size-16" />
|
||||
@@ -186,34 +186,36 @@ export default function SearchView({
|
||||
scrollLock={false}
|
||||
onClick={onSelectSearch}
|
||||
/>
|
||||
<div className={cn("absolute right-2 top-2 z-40")}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Chip
|
||||
className={`flex select-none items-center justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize text-white`}
|
||||
>
|
||||
{value.search_source == "thumbnail" ? (
|
||||
<LuImage className="mr-1 size-3" />
|
||||
) : (
|
||||
<LuText className="mr-1 size-3" />
|
||||
)}
|
||||
{searchTerm && (
|
||||
<div className={cn("absolute right-2 top-2 z-40")}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Chip
|
||||
className={`flex select-none items-center justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize text-white`}
|
||||
>
|
||||
{value.search_source == "thumbnail" ? (
|
||||
<LuImage className="mr-1 size-3" />
|
||||
) : (
|
||||
<LuText className="mr-1 size-3" />
|
||||
)}
|
||||
{zScoreToConfidence(
|
||||
value.search_distance,
|
||||
value.search_source,
|
||||
)}
|
||||
%
|
||||
</Chip>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Matched {value.search_source} at{" "}
|
||||
{zScoreToConfidence(
|
||||
value.search_distance,
|
||||
value.search_source,
|
||||
)}
|
||||
%
|
||||
</Chip>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Matched {value.search_source} at{" "}
|
||||
{zScoreToConfidence(
|
||||
value.search_distance,
|
||||
value.search_source,
|
||||
)}
|
||||
%
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`review-item-ring pointer-events-none absolute inset-0 z-10 size-full rounded-lg outline outline-[3px] -outline-offset-[2.8px] ${selected ? `shadow-severity_alert outline-severity_alert` : "outline-transparent duration-500"}`}
|
||||
|
||||
Reference in New Issue
Block a user