mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Search and search filter UI tweaks (#14381)
* fix search type switches * select/unselect style for more filters button * fix reset button * fix labels scrollbar * set min width and remove modal to allow scrolling with filters open * hover colors * better match of font size * stop sheet from displaying console errors * fix detail dialog behavior
This commit is contained in:
parent
3f1ab66899
commit
eda52a3b82
@ -113,7 +113,7 @@ export default function SearchThumbnailFooter({
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col items-start text-xs">
|
||||
<div className="flex flex-col items-start text-xs text-primary-variant">
|
||||
{searchResult.end_time ? (
|
||||
<TimeAgo time={searchResult.start_time * 1000} dense />
|
||||
) : (
|
||||
@ -132,7 +132,7 @@ export default function SearchThumbnailFooter({
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<FrigatePlusIcon
|
||||
className="size-5 cursor-pointer text-primary"
|
||||
className="size-5 cursor-pointer text-primary-variant hover:text-primary"
|
||||
onClick={() => setShowFrigatePlus(true)}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
@ -144,7 +144,7 @@ export default function SearchThumbnailFooter({
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<MdImageSearch
|
||||
className="size-5 cursor-pointer text-primary"
|
||||
className="size-5 cursor-pointer text-primary-variant hover:text-primary"
|
||||
onClick={findSimilar}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
@ -154,7 +154,7 @@ export default function SearchThumbnailFooter({
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<LuMoreVertical className="size-5 cursor-pointer text-primary" />
|
||||
<LuMoreVertical className="size-5 cursor-pointer text-primary-variant hover:text-primary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align={"end"}>
|
||||
{searchResult.has_clip && (
|
||||
|
@ -253,7 +253,11 @@ function GeneralFilterButton({
|
||||
<PlatformAwareDialog
|
||||
trigger={trigger}
|
||||
content={content}
|
||||
contentClassName={isDesktop ? "" : "max-h-[75dvh] overflow-hidden p-4"}
|
||||
contentClassName={
|
||||
isDesktop
|
||||
? "scrollbar-container h-auto max-h-[80dvh] overflow-y-auto"
|
||||
: "max-h-[75dvh] overflow-hidden p-4"
|
||||
}
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
@ -284,7 +288,7 @@ export function GeneralFilterContent({
|
||||
}: GeneralFilterContentProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||
<div className="overflow-x-hidden">
|
||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||
<Label
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
|
@ -102,7 +102,9 @@ export default function SearchDetailDialog({
|
||||
const [isOpen, setIsOpen] = useState(search != undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
setIsOpen(search != undefined);
|
||||
}
|
||||
}, [search]);
|
||||
|
||||
const searchTabs = useMemo(() => {
|
||||
@ -122,12 +124,6 @@ export default function SearchDetailDialog({
|
||||
views.splice(index, 1);
|
||||
}
|
||||
|
||||
// TODO implement
|
||||
//if (!config.semantic_search.enabled) {
|
||||
// const index = views.indexOf("similar-calendar");
|
||||
// views.splice(index, 1);
|
||||
// }
|
||||
|
||||
return views;
|
||||
}, [config, search]);
|
||||
|
||||
@ -154,14 +150,7 @@ export default function SearchDetailDialog({
|
||||
const Description = isDesktop ? DialogDescription : MobilePageDescription;
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setSearch(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Overlay open={isOpen} onOpenChange={() => setIsOpen(!isOpen)}>
|
||||
<Content
|
||||
className={cn(
|
||||
"scrollbar-container overflow-y-auto",
|
||||
|
@ -10,7 +10,14 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { isMobile } from "react-device-detect";
|
||||
|
||||
type PlatformAwareDialogProps = {
|
||||
@ -52,16 +59,20 @@ export default function PlatformAwareDialog({
|
||||
|
||||
type PlatformAwareSheetProps = {
|
||||
trigger: JSX.Element;
|
||||
title?: string | JSX.Element;
|
||||
content: JSX.Element;
|
||||
triggerClassName?: string;
|
||||
titleClassName?: string;
|
||||
contentClassName?: string;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
};
|
||||
export function PlatformAwareSheet({
|
||||
trigger,
|
||||
title,
|
||||
content,
|
||||
triggerClassName = "",
|
||||
titleClassName = "",
|
||||
contentClassName = "",
|
||||
open,
|
||||
onOpenChange,
|
||||
@ -86,11 +97,19 @@ export function PlatformAwareSheet({
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<Sheet open={open} onOpenChange={onOpenChange} modal={false}>
|
||||
<SheetTrigger asChild className={triggerClassName}>
|
||||
{trigger}
|
||||
</SheetTrigger>
|
||||
<SheetContent className={contentClassName}>{content}</SheetContent>
|
||||
<SheetContent className={contentClassName}>
|
||||
<SheetHeader>
|
||||
<SheetTitle className={title ? titleClassName : "sr-only"}>
|
||||
{title ?? ""}
|
||||
</SheetTitle>
|
||||
<SheetDescription className="sr-only">Information</SheetDescription>
|
||||
</SheetHeader>
|
||||
{content}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import {
|
||||
} from "@/components/ui/popover";
|
||||
import { isDesktop, isMobileOnly } from "react-device-detect";
|
||||
import { useFormattedHour } from "@/hooks/use-date-utils";
|
||||
import Heading from "@/components/ui/heading";
|
||||
import FilterSwitch from "@/components/filter/FilterSwitch";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@ -51,9 +50,27 @@ export default function SearchFilterDialog({
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const moreFiltersSelected = useMemo(
|
||||
() =>
|
||||
currentFilter &&
|
||||
(currentFilter.time_range ||
|
||||
(currentFilter.zones?.length ?? 0) > 0 ||
|
||||
(currentFilter.sub_labels?.length ?? 0) > 0 ||
|
||||
(currentFilter.search_type?.length ?? 2) !== 2),
|
||||
[currentFilter],
|
||||
);
|
||||
|
||||
const trigger = (
|
||||
<Button className="flex items-center gap-2" size="sm">
|
||||
<FaCog className={"text-secondary-foreground"} />
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
variant={moreFiltersSelected ? "select" : "default"}
|
||||
>
|
||||
<FaCog
|
||||
className={cn(
|
||||
moreFiltersSelected ? "text-white" : "text-secondary-foreground",
|
||||
)}
|
||||
/>
|
||||
More Filters
|
||||
</Button>
|
||||
);
|
||||
@ -80,14 +97,20 @@ export default function SearchFilterDialog({
|
||||
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
||||
}
|
||||
/>
|
||||
{config?.semantic_search?.enabled &&
|
||||
!currentFilter?.search_type?.includes("similarity") && (
|
||||
<SearchTypeContent
|
||||
searchSources={
|
||||
currentFilter?.search_type ?? ["thumbnail", "description"]
|
||||
}
|
||||
setSearchSources={(newSearchSource) =>
|
||||
onUpdateFilter({ ...currentFilter, search_type: newSearchSource })
|
||||
setCurrentFilter({
|
||||
...currentFilter,
|
||||
search_type: newSearchSource,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isDesktop && <DropdownMenuSeparator />}
|
||||
<div className="flex items-center justify-evenly p-2">
|
||||
<Button
|
||||
@ -104,7 +127,13 @@ export default function SearchFilterDialog({
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCurrentFilter(filter ?? {});
|
||||
setCurrentFilter((prevFilter) => ({
|
||||
...prevFilter,
|
||||
time_range: undefined,
|
||||
zones: undefined,
|
||||
sub_labels: undefined,
|
||||
search_type: ["thumbnail", "description"],
|
||||
}));
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
@ -118,7 +147,7 @@ export default function SearchFilterDialog({
|
||||
trigger={trigger}
|
||||
content={content}
|
||||
contentClassName={cn(
|
||||
"w-auto lg:w-[300px] scrollbar-container h-full overflow-auto px-4",
|
||||
"w-auto lg:min-w-[275px] scrollbar-container h-full overflow-auto px-4",
|
||||
isMobileOnly && "pb-20",
|
||||
)}
|
||||
open={open}
|
||||
@ -184,8 +213,8 @@ function TimeRangeFilterContent({
|
||||
|
||||
return (
|
||||
<div className="overflow-x-hidden">
|
||||
<Heading as="h4">Time Range</Heading>
|
||||
<div className="my-3 flex flex-row items-center justify-center gap-2">
|
||||
<div className="text-lg">Time Range</div>
|
||||
<div className="mt-3 flex flex-row items-center justify-center gap-2">
|
||||
<Popover
|
||||
open={startOpen}
|
||||
onOpenChange={(open) => {
|
||||
@ -280,7 +309,7 @@ export function ZoneFilterContent({
|
||||
<>
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<Heading as="h4">Zones</Heading>
|
||||
<div className="text-lg">Zones</div>
|
||||
{allZones && (
|
||||
<>
|
||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||
@ -301,7 +330,7 @@ export function ZoneFilterContent({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
<div className="mt-2.5 flex flex-col gap-2.5">
|
||||
{allZones.map((item) => (
|
||||
<FilterSwitch
|
||||
key={item}
|
||||
@ -346,7 +375,7 @@ export function SubFilterContent({
|
||||
return (
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<Heading as="h4">Sub Labels</Heading>
|
||||
<div className="text-lg">Sub Labels</div>
|
||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||
<Label className="mx-2 cursor-pointer text-primary" htmlFor="allLabels">
|
||||
All Sub Labels
|
||||
@ -362,7 +391,7 @@ export function SubFilterContent({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
<div className="mt-2.5 flex flex-col gap-2.5">
|
||||
{allSubLabels.map((item) => (
|
||||
<FilterSwitch
|
||||
key={item}
|
||||
@ -403,8 +432,8 @@ export function SearchTypeContent({
|
||||
<>
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<Heading as="h4">Search Sources</Heading>
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
<div className="text-lg">Search Sources</div>
|
||||
<div className="mt-2.5 flex flex-col gap-2.5">
|
||||
<FilterSwitch
|
||||
label="Thumbnail Image"
|
||||
isChecked={searchSources?.includes("thumbnail") ?? false}
|
||||
|
Loading…
Reference in New Issue
Block a user