mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-09 15:05:26 +03:00
frontend
This commit is contained in:
parent
3dcef62271
commit
7e4af87c1c
@ -333,13 +333,18 @@ function ObjectDetailsTab({
|
|||||||
}
|
}
|
||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
const identifierScore = useMemo(() => {
|
const recognizedLicensePlateScore = useMemo(() => {
|
||||||
if (!search) {
|
if (!search) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search.data.identifier && search.data?.identifier_score) {
|
if (
|
||||||
return Math.round((search.data?.identifier_score ?? 0) * 100);
|
search.data.recognized_license_plate &&
|
||||||
|
search.data?.recognized_license_plate_score
|
||||||
|
) {
|
||||||
|
return Math.round(
|
||||||
|
(search.data?.recognized_license_plate_score ?? 0) * 100,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -550,13 +555,16 @@ function ObjectDetailsTab({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{search?.data.identifier && (
|
{search?.data.recognized_license_plate && (
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<div className="text-sm text-primary/40">Identifier</div>
|
<div className="text-sm text-primary/40">
|
||||||
|
Recognized License Plate
|
||||||
|
</div>
|
||||||
<div className="flex flex-col space-y-0.5 text-sm">
|
<div className="flex flex-col space-y-0.5 text-sm">
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
{search.data.identifier}{" "}
|
{search.data.recognized_license_plate}{" "}
|
||||||
{identifierScore && ` (${identifierScore}%)`}
|
{recognizedLicensePlateScore &&
|
||||||
|
` (${recognizedLicensePlateScore}%)`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export default function SearchFilterDialog({
|
|||||||
(currentFilter.max_speed ?? 150) < 150 ||
|
(currentFilter.max_speed ?? 150) < 150 ||
|
||||||
(currentFilter.zones?.length ?? 0) > 0 ||
|
(currentFilter.zones?.length ?? 0) > 0 ||
|
||||||
(currentFilter.sub_labels?.length ?? 0) > 0 ||
|
(currentFilter.sub_labels?.length ?? 0) > 0 ||
|
||||||
(currentFilter.identifier?.length ?? 0) > 0),
|
(currentFilter.recognized_license_plate?.length ?? 0) > 0),
|
||||||
[currentFilter],
|
[currentFilter],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -128,10 +128,13 @@ export default function SearchFilterDialog({
|
|||||||
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<IdentifierFilterContent
|
<RecognizedLicensePlatesFilterContent
|
||||||
identifiers={currentFilter.identifier}
|
recognizedLicensePlates={currentFilter.recognized_license_plate}
|
||||||
setIdentifiers={(identifiers) =>
|
setRecognizedLicensePlates={(plate) =>
|
||||||
setCurrentFilter({ ...currentFilter, identifier: identifiers })
|
setCurrentFilter({
|
||||||
|
...currentFilter,
|
||||||
|
recognized_license_plate: plate,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ScoreFilterContent
|
<ScoreFilterContent
|
||||||
@ -207,7 +210,7 @@ export default function SearchFilterDialog({
|
|||||||
max_speed: undefined,
|
max_speed: undefined,
|
||||||
has_snapshot: undefined,
|
has_snapshot: undefined,
|
||||||
has_clip: undefined,
|
has_clip: undefined,
|
||||||
identifier: undefined,
|
recognized_license_plate: undefined,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -847,97 +850,109 @@ export function SnapshotClipFilterContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type IdentifierFilterContentProps = {
|
type RecognizedLicensePlatesFilterContentProps = {
|
||||||
identifiers: string[] | undefined;
|
recognizedLicensePlates: string[] | undefined;
|
||||||
setIdentifiers: (identifiers: string[] | undefined) => void;
|
setRecognizedLicensePlates: (
|
||||||
|
recognizedLicensePlates: string[] | undefined,
|
||||||
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function IdentifierFilterContent({
|
export function RecognizedLicensePlatesFilterContent({
|
||||||
identifiers,
|
recognizedLicensePlates,
|
||||||
setIdentifiers,
|
setRecognizedLicensePlates,
|
||||||
}: IdentifierFilterContentProps) {
|
}: RecognizedLicensePlatesFilterContentProps) {
|
||||||
const { data: allIdentifiers, error } = useSWR<string[]>("identifiers", {
|
const { data: allRecognizedLicensePlates, error } = useSWR<string[]>(
|
||||||
|
"recognized_license_plates",
|
||||||
|
{
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
});
|
},
|
||||||
|
|
||||||
const [selectedIdentifiers, setSelectedIdentifiers] = useState<string[]>(
|
|
||||||
identifiers || [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [selectedRecognizedLicensePlates, setSelectedRecognizedLicensePlates] =
|
||||||
|
useState<string[]>(recognizedLicensePlates || []);
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (identifiers) {
|
if (recognizedLicensePlates) {
|
||||||
setSelectedIdentifiers(identifiers);
|
setSelectedRecognizedLicensePlates(recognizedLicensePlates);
|
||||||
} else {
|
} else {
|
||||||
setSelectedIdentifiers([]);
|
setSelectedRecognizedLicensePlates([]);
|
||||||
}
|
}
|
||||||
}, [identifiers]);
|
}, [recognizedLicensePlates]);
|
||||||
|
|
||||||
const handleSelect = (identifier: string) => {
|
const handleSelect = (recognizedLicensePlate: string) => {
|
||||||
const newSelected = selectedIdentifiers.includes(identifier)
|
const newSelected = selectedRecognizedLicensePlates.includes(
|
||||||
? selectedIdentifiers.filter((id) => id !== identifier) // Deselect
|
recognizedLicensePlate,
|
||||||
: [...selectedIdentifiers, identifier]; // Select
|
)
|
||||||
|
? selectedRecognizedLicensePlates.filter(
|
||||||
|
(id) => id !== recognizedLicensePlate,
|
||||||
|
) // Deselect
|
||||||
|
: [...selectedRecognizedLicensePlates, recognizedLicensePlate]; // Select
|
||||||
|
|
||||||
setSelectedIdentifiers(newSelected);
|
setSelectedRecognizedLicensePlates(newSelected);
|
||||||
if (newSelected.length === 0) {
|
if (newSelected.length === 0) {
|
||||||
setIdentifiers(undefined); // Clear filter if no identifiers selected
|
setRecognizedLicensePlates(undefined); // Clear filter if no plates selected
|
||||||
} else {
|
} else {
|
||||||
setIdentifiers(newSelected);
|
setRecognizedLicensePlates(newSelected);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!allIdentifiers || allIdentifiers.length === 0) {
|
if (!allRecognizedLicensePlates || allRecognizedLicensePlates.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredIdentifiers =
|
const filteredRecognizedLicensePlates =
|
||||||
allIdentifiers?.filter((id) =>
|
allRecognizedLicensePlates?.filter((id) =>
|
||||||
id.toLowerCase().includes(inputValue.toLowerCase()),
|
id.toLowerCase().includes(inputValue.toLowerCase()),
|
||||||
) || [];
|
) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-hidden">
|
<div className="overflow-x-hidden">
|
||||||
<DropdownMenuSeparator className="mb-3" />
|
<DropdownMenuSeparator className="mb-3" />
|
||||||
<div className="mb-3 text-lg">Identifiers</div>
|
<div className="mb-3 text-lg">Recognized License Plates</div>
|
||||||
{error ? (
|
{error ? (
|
||||||
<p className="text-sm text-red-500">Failed to load identifiers</p>
|
<p className="text-sm text-red-500">
|
||||||
) : !allIdentifiers ? (
|
Failed to load recognized license plates.
|
||||||
<p className="text-sm text-muted-foreground">Loading identifiers...</p>
|
</p>
|
||||||
|
) : !allRecognizedLicensePlates ? (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Loading recognized license plates...
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Command className="border border-input bg-background">
|
<Command className="border border-input bg-background">
|
||||||
<CommandInput
|
<CommandInput
|
||||||
placeholder="Type to search identifiers..."
|
placeholder="Type to search license plates..."
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onValueChange={setInputValue}
|
onValueChange={setInputValue}
|
||||||
/>
|
/>
|
||||||
<CommandList className="max-h-[200px] overflow-auto">
|
<CommandList className="max-h-[200px] overflow-auto">
|
||||||
{filteredIdentifiers.length === 0 && inputValue && (
|
{filteredRecognizedLicensePlates.length === 0 && inputValue && (
|
||||||
<CommandEmpty>No identifiers found.</CommandEmpty>
|
<CommandEmpty>No license plates found.</CommandEmpty>
|
||||||
)}
|
)}
|
||||||
{filteredIdentifiers.map((identifier) => (
|
{filteredRecognizedLicensePlates.map((plate) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={identifier}
|
key={plate}
|
||||||
value={identifier}
|
value={plate}
|
||||||
onSelect={() => handleSelect(identifier)}
|
onSelect={() => handleSelect(plate)}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
<LuCheck
|
<LuCheck
|
||||||
className={cn(
|
className={cn(
|
||||||
"mr-2 h-4 w-4",
|
"mr-2 h-4 w-4",
|
||||||
selectedIdentifiers.includes(identifier)
|
selectedRecognizedLicensePlates.includes(plate)
|
||||||
? "opacity-100"
|
? "opacity-100"
|
||||||
: "opacity-0",
|
: "opacity-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{identifier}
|
{plate}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
{selectedIdentifiers.length > 0 && (
|
{selectedRecognizedLicensePlates.length > 0 && (
|
||||||
<div className="mt-2 flex flex-wrap gap-2">
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
{selectedIdentifiers.map((id) => (
|
{selectedRecognizedLicensePlates.map((id) => (
|
||||||
<span
|
<span
|
||||||
key={id}
|
key={id}
|
||||||
className="inline-flex items-center rounded bg-selected px-2 py-1 text-sm text-white"
|
className="inline-flex items-center rounded bg-selected px-2 py-1 text-sm text-white"
|
||||||
@ -956,7 +971,7 @@ export function IdentifierFilterContent({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
Select one or more identifiers from the list.
|
Select one or more plates from the list.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -105,7 +105,8 @@ export default function Explore() {
|
|||||||
cameras: searchSearchParams["cameras"],
|
cameras: searchSearchParams["cameras"],
|
||||||
labels: searchSearchParams["labels"],
|
labels: searchSearchParams["labels"],
|
||||||
sub_labels: searchSearchParams["sub_labels"],
|
sub_labels: searchSearchParams["sub_labels"],
|
||||||
identifier: searchSearchParams["identifier"],
|
recognized_license_plate:
|
||||||
|
searchSearchParams["recognized_license_plate"],
|
||||||
zones: searchSearchParams["zones"],
|
zones: searchSearchParams["zones"],
|
||||||
before: searchSearchParams["before"],
|
before: searchSearchParams["before"],
|
||||||
after: searchSearchParams["after"],
|
after: searchSearchParams["after"],
|
||||||
@ -141,7 +142,8 @@ export default function Explore() {
|
|||||||
cameras: searchSearchParams["cameras"],
|
cameras: searchSearchParams["cameras"],
|
||||||
labels: searchSearchParams["labels"],
|
labels: searchSearchParams["labels"],
|
||||||
sub_labels: searchSearchParams["sub_labels"],
|
sub_labels: searchSearchParams["sub_labels"],
|
||||||
identifier: searchSearchParams["identifier"],
|
recognized_license_plate:
|
||||||
|
searchSearchParams["recognized_license_plate"],
|
||||||
zones: searchSearchParams["zones"],
|
zones: searchSearchParams["zones"],
|
||||||
before: searchSearchParams["before"],
|
before: searchSearchParams["before"],
|
||||||
after: searchSearchParams["after"],
|
after: searchSearchParams["after"],
|
||||||
|
|||||||
@ -58,8 +58,8 @@ export type SearchResult = {
|
|||||||
average_estimated_speed: number;
|
average_estimated_speed: number;
|
||||||
velocity_angle: number;
|
velocity_angle: number;
|
||||||
path_data: [number[], number][];
|
path_data: [number[], number][];
|
||||||
identifier?: string;
|
recognized_license_plate?: string;
|
||||||
identifier_score?: number;
|
recognized_license_plate_score?: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ export type SearchFilter = {
|
|||||||
cameras?: string[];
|
cameras?: string[];
|
||||||
labels?: string[];
|
labels?: string[];
|
||||||
sub_labels?: string[];
|
sub_labels?: string[];
|
||||||
identifier?: string[];
|
recognized_license_plate?: string[];
|
||||||
zones?: string[];
|
zones?: string[];
|
||||||
before?: number;
|
before?: number;
|
||||||
after?: number;
|
after?: number;
|
||||||
@ -92,7 +92,7 @@ export type SearchQueryParams = {
|
|||||||
cameras?: string[];
|
cameras?: string[];
|
||||||
labels?: string[];
|
labels?: string[];
|
||||||
sub_labels?: string[];
|
sub_labels?: string[];
|
||||||
identifier?: string[];
|
recognized_license_plate?: string[];
|
||||||
zones?: string[];
|
zones?: string[];
|
||||||
before?: string;
|
before?: string;
|
||||||
after?: string;
|
after?: string;
|
||||||
|
|||||||
@ -121,7 +121,9 @@ export default function SearchView({
|
|||||||
}, [config, searchFilter]);
|
}, [config, searchFilter]);
|
||||||
|
|
||||||
const { data: allSubLabels } = useSWR("sub_labels");
|
const { data: allSubLabels } = useSWR("sub_labels");
|
||||||
const { data: allIdentifiers } = useSWR("identifiers");
|
const { data: allRecognizedLicensePlates } = useSWR(
|
||||||
|
"recognized_license_plate",
|
||||||
|
);
|
||||||
|
|
||||||
const allZones = useMemo<string[]>(() => {
|
const allZones = useMemo<string[]>(() => {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@ -161,13 +163,20 @@ export default function SearchView({
|
|||||||
max_score: ["100"],
|
max_score: ["100"],
|
||||||
min_speed: ["1"],
|
min_speed: ["1"],
|
||||||
max_speed: ["150"],
|
max_speed: ["150"],
|
||||||
identifier: allIdentifiers,
|
recognized_license_plate: allRecognizedLicensePlates,
|
||||||
has_clip: ["yes", "no"],
|
has_clip: ["yes", "no"],
|
||||||
has_snapshot: ["yes", "no"],
|
has_snapshot: ["yes", "no"],
|
||||||
...(config?.plus?.enabled &&
|
...(config?.plus?.enabled &&
|
||||||
searchFilter?.has_snapshot && { is_submitted: ["yes", "no"] }),
|
searchFilter?.has_snapshot && { is_submitted: ["yes", "no"] }),
|
||||||
}),
|
}),
|
||||||
[config, allLabels, allZones, allSubLabels, allIdentifiers, searchFilter],
|
[
|
||||||
|
config,
|
||||||
|
allLabels,
|
||||||
|
allZones,
|
||||||
|
allSubLabels,
|
||||||
|
allRecognizedLicensePlates,
|
||||||
|
searchFilter,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove duplicate event ids
|
// remove duplicate event ids
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user