mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Semantic search UI tweaks (#13591)
* Semantic search UI tweaks * clean up
This commit is contained in:
parent
17fa830851
commit
d6b16a7747
@ -12,6 +12,7 @@ import { isMobile } from "react-device-detect";
|
|||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
import { DateRangePicker } from "../ui/calendar-range";
|
import { DateRangePicker } from "../ui/calendar-range";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
type CalendarFilterButtonProps = {
|
type CalendarFilterButtonProps = {
|
||||||
reviewSummary?: ReviewSummary;
|
reviewSummary?: ReviewSummary;
|
||||||
@ -91,6 +92,8 @@ export function CalendarRangeFilterButton({
|
|||||||
defaultText,
|
defaultText,
|
||||||
updateSelectedRange,
|
updateSelectedRange,
|
||||||
}: CalendarRangeFilterButtonProps) {
|
}: CalendarRangeFilterButtonProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const selectedDate = useFormattedRange(
|
const selectedDate = useFormattedRange(
|
||||||
range?.from == undefined ? 0 : range.from.getTime() / 1000 + 1,
|
range?.from == undefined ? 0 : range.from.getTime() / 1000 + 1,
|
||||||
range?.to == undefined ? 0 : range.to.getTime() / 1000 - 1,
|
range?.to == undefined ? 0 : range.to.getTime() / 1000 - 1,
|
||||||
@ -119,24 +122,18 @@ export function CalendarRangeFilterButton({
|
|||||||
initialDateFrom={range?.from}
|
initialDateFrom={range?.from}
|
||||||
initialDateTo={range?.to}
|
initialDateTo={range?.to}
|
||||||
showCompare={false}
|
showCompare={false}
|
||||||
onUpdate={(range) => updateSelectedRange(range.range)}
|
onUpdate={(range) => {
|
||||||
|
updateSelectedRange(range.range);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
onReset={() => updateSelectedRange(undefined)}
|
||||||
/>
|
/>
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<div className="flex items-center justify-center p-2">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
updateSelectedRange(undefined);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
<Drawer>
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
|
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
|
||||||
<DrawerContent>{content}</DrawerContent>
|
<DrawerContent>{content}</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
@ -144,9 +141,9 @@ export function CalendarRangeFilterButton({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
||||||
<PopoverContent className="w-[840px]">{content}</PopoverContent>
|
<PopoverContent className="w-auto">{content}</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
/* eslint-disable max-lines */
|
import { useState, useEffect, useRef } from "react";
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { type FC, useState, useEffect, useRef } from "react";
|
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { Calendar } from "./calendar";
|
import { Calendar } from "./calendar";
|
||||||
import { Label } from "./label";
|
import { Label } from "./label";
|
||||||
@ -19,6 +16,7 @@ import { LuCheck } from "react-icons/lu";
|
|||||||
export interface DateRangePickerProps {
|
export interface DateRangePickerProps {
|
||||||
/** Click handler for applying the updates from DateRangePicker. */
|
/** Click handler for applying the updates from DateRangePicker. */
|
||||||
onUpdate?: (values: { range: DateRange; rangeCompare?: DateRange }) => void;
|
onUpdate?: (values: { range: DateRange; rangeCompare?: DateRange }) => void;
|
||||||
|
onReset?: () => void;
|
||||||
/** Initial value for start date */
|
/** Initial value for start date */
|
||||||
initialDateFrom?: Date | string;
|
initialDateFrom?: Date | string;
|
||||||
/** Initial value for end date */
|
/** Initial value for end date */
|
||||||
@ -73,14 +71,15 @@ const PRESETS: Preset[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/** The DateRangePicker component allows a user to select a range of dates */
|
/** The DateRangePicker component allows a user to select a range of dates */
|
||||||
export const DateRangePicker: FC<DateRangePickerProps> = ({
|
export function DateRangePicker({
|
||||||
initialDateFrom = new Date(new Date().setHours(0, 0, 0, 0)),
|
initialDateFrom = new Date(new Date().setHours(0, 0, 0, 0)),
|
||||||
initialDateTo,
|
initialDateTo,
|
||||||
initialCompareFrom,
|
initialCompareFrom,
|
||||||
initialCompareTo,
|
initialCompareTo,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
onReset,
|
||||||
showCompare = true,
|
showCompare = true,
|
||||||
}): JSX.Element => {
|
}: DateRangePickerProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const [range, setRange] = useState<DateRange>({
|
const [range, setRange] = useState<DateRange>({
|
||||||
@ -283,7 +282,7 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}): JSX.Element => (
|
}): JSX.Element => (
|
||||||
<Button
|
<Button
|
||||||
className={cn(isSelected && "pointer-events-none text-white")}
|
className={cn(isSelected && "pointer-events-none text-primary")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPreset(preset);
|
setPreset(preset);
|
||||||
@ -415,17 +414,19 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2 py-2 pr-4">
|
<div className="flex justify-center gap-2 py-2 pr-4">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
resetValues();
|
resetValues();
|
||||||
|
onReset?.();
|
||||||
}}
|
}}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
Cancel
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
variant="select"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
if (
|
if (
|
||||||
@ -436,9 +437,9 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Update
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
@ -8,7 +8,13 @@ import { cn } from "@/lib/utils";
|
|||||||
import { Preview } from "@/types/preview";
|
import { Preview } from "@/types/preview";
|
||||||
import { SearchFilter, SearchResult } from "@/types/search";
|
import { SearchFilter, SearchResult } from "@/types/search";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { LuSearchCheck, LuSearchX } from "react-icons/lu";
|
import {
|
||||||
|
LuExternalLink,
|
||||||
|
LuSearchCheck,
|
||||||
|
LuSearchX,
|
||||||
|
LuXCircle,
|
||||||
|
} from "react-icons/lu";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
type SearchViewProps = {
|
type SearchViewProps = {
|
||||||
search: string;
|
search: string;
|
||||||
@ -63,12 +69,20 @@ 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 mb-2 flex h-11 items-center justify-between pl-2 pr-2 md:pl-3">
|
||||||
<Input
|
<div className="relative w-full md:w-1/3">
|
||||||
className={"text-md mr-2 w-full bg-muted md:mr-0 md:w-1/3"}
|
<Input
|
||||||
placeholder="Search for a specific detection..."
|
className="text-md w-full bg-muted pr-10"
|
||||||
value={search}
|
placeholder="Search for a specific detected object..."
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
value={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>
|
||||||
|
|
||||||
<SearchFilterGroup
|
<SearchFilterGroup
|
||||||
filter={searchFilter}
|
filter={searchFilter}
|
||||||
@ -80,14 +94,28 @@ export default function SearchView({
|
|||||||
{searchTerm.length == 0 && (
|
{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">
|
<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" />
|
<LuSearchCheck className="size-16" />
|
||||||
Search For Detections
|
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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{searchTerm.length > 0 && searchResults?.length == 0 && (
|
{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">
|
<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" />
|
<LuSearchX className="size-16" />
|
||||||
No Detections Found
|
No Detected Objects Found
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user