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 ? (
|
{searchResult.end_time ? (
|
||||||
<TimeAgo time={searchResult.start_time * 1000} dense />
|
<TimeAgo time={searchResult.start_time * 1000} dense />
|
||||||
) : (
|
) : (
|
||||||
@ -132,7 +132,7 @@ export default function SearchThumbnailFooter({
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<FrigatePlusIcon
|
<FrigatePlusIcon
|
||||||
className="size-5 cursor-pointer text-primary"
|
className="size-5 cursor-pointer text-primary-variant hover:text-primary"
|
||||||
onClick={() => setShowFrigatePlus(true)}
|
onClick={() => setShowFrigatePlus(true)}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@ -144,7 +144,7 @@ export default function SearchThumbnailFooter({
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<MdImageSearch
|
<MdImageSearch
|
||||||
className="size-5 cursor-pointer text-primary"
|
className="size-5 cursor-pointer text-primary-variant hover:text-primary"
|
||||||
onClick={findSimilar}
|
onClick={findSimilar}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@ -154,7 +154,7 @@ export default function SearchThumbnailFooter({
|
|||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<LuMoreVertical className="size-5 cursor-pointer text-primary" />
|
<LuMoreVertical className="size-5 cursor-pointer text-primary-variant hover:text-primary" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align={"end"}>
|
<DropdownMenuContent align={"end"}>
|
||||||
{searchResult.has_clip && (
|
{searchResult.has_clip && (
|
||||||
|
@ -253,7 +253,11 @@ function GeneralFilterButton({
|
|||||||
<PlatformAwareDialog
|
<PlatformAwareDialog
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
content={content}
|
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}
|
open={open}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
@ -284,7 +288,7 @@ export function GeneralFilterContent({
|
|||||||
}: GeneralFilterContentProps) {
|
}: GeneralFilterContentProps) {
|
||||||
return (
|
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">
|
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||||
<Label
|
<Label
|
||||||
className="mx-2 cursor-pointer text-primary"
|
className="mx-2 cursor-pointer text-primary"
|
||||||
|
@ -102,7 +102,9 @@ export default function SearchDetailDialog({
|
|||||||
const [isOpen, setIsOpen] = useState(search != undefined);
|
const [isOpen, setIsOpen] = useState(search != undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (search) {
|
||||||
setIsOpen(search != undefined);
|
setIsOpen(search != undefined);
|
||||||
|
}
|
||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
const searchTabs = useMemo(() => {
|
const searchTabs = useMemo(() => {
|
||||||
@ -122,12 +124,6 @@ export default function SearchDetailDialog({
|
|||||||
views.splice(index, 1);
|
views.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO implement
|
|
||||||
//if (!config.semantic_search.enabled) {
|
|
||||||
// const index = views.indexOf("similar-calendar");
|
|
||||||
// views.splice(index, 1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return views;
|
return views;
|
||||||
}, [config, search]);
|
}, [config, search]);
|
||||||
|
|
||||||
@ -154,14 +150,7 @@ export default function SearchDetailDialog({
|
|||||||
const Description = isDesktop ? DialogDescription : MobilePageDescription;
|
const Description = isDesktop ? DialogDescription : MobilePageDescription;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay open={isOpen} onOpenChange={() => setIsOpen(!isOpen)}>
|
||||||
open={isOpen}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
if (!open) {
|
|
||||||
setSearch(undefined);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Content
|
<Content
|
||||||
className={cn(
|
className={cn(
|
||||||
"scrollbar-container overflow-y-auto",
|
"scrollbar-container overflow-y-auto",
|
||||||
|
@ -10,7 +10,14 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} 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";
|
import { isMobile } from "react-device-detect";
|
||||||
|
|
||||||
type PlatformAwareDialogProps = {
|
type PlatformAwareDialogProps = {
|
||||||
@ -52,16 +59,20 @@ export default function PlatformAwareDialog({
|
|||||||
|
|
||||||
type PlatformAwareSheetProps = {
|
type PlatformAwareSheetProps = {
|
||||||
trigger: JSX.Element;
|
trigger: JSX.Element;
|
||||||
|
title?: string | JSX.Element;
|
||||||
content: JSX.Element;
|
content: JSX.Element;
|
||||||
triggerClassName?: string;
|
triggerClassName?: string;
|
||||||
|
titleClassName?: string;
|
||||||
contentClassName?: string;
|
contentClassName?: string;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
export function PlatformAwareSheet({
|
export function PlatformAwareSheet({
|
||||||
trigger,
|
trigger,
|
||||||
|
title,
|
||||||
content,
|
content,
|
||||||
triggerClassName = "",
|
triggerClassName = "",
|
||||||
|
titleClassName = "",
|
||||||
contentClassName = "",
|
contentClassName = "",
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
@ -86,11 +97,19 @@ export function PlatformAwareSheet({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
<Sheet open={open} onOpenChange={onOpenChange} modal={false}>
|
||||||
<SheetTrigger asChild className={triggerClassName}>
|
<SheetTrigger asChild className={triggerClassName}>
|
||||||
{trigger}
|
{trigger}
|
||||||
</SheetTrigger>
|
</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>
|
</Sheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { isDesktop, isMobileOnly } from "react-device-detect";
|
import { isDesktop, isMobileOnly } from "react-device-detect";
|
||||||
import { useFormattedHour } from "@/hooks/use-date-utils";
|
import { useFormattedHour } from "@/hooks/use-date-utils";
|
||||||
import Heading from "@/components/ui/heading";
|
|
||||||
import FilterSwitch from "@/components/filter/FilterSwitch";
|
import FilterSwitch from "@/components/filter/FilterSwitch";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@ -51,9 +50,27 @@ export default function SearchFilterDialog({
|
|||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
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 = (
|
const trigger = (
|
||||||
<Button className="flex items-center gap-2" size="sm">
|
<Button
|
||||||
<FaCog className={"text-secondary-foreground"} />
|
className="flex items-center gap-2"
|
||||||
|
size="sm"
|
||||||
|
variant={moreFiltersSelected ? "select" : "default"}
|
||||||
|
>
|
||||||
|
<FaCog
|
||||||
|
className={cn(
|
||||||
|
moreFiltersSelected ? "text-white" : "text-secondary-foreground",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
More Filters
|
More Filters
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@ -80,14 +97,20 @@ export default function SearchFilterDialog({
|
|||||||
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{config?.semantic_search?.enabled &&
|
||||||
|
!currentFilter?.search_type?.includes("similarity") && (
|
||||||
<SearchTypeContent
|
<SearchTypeContent
|
||||||
searchSources={
|
searchSources={
|
||||||
currentFilter?.search_type ?? ["thumbnail", "description"]
|
currentFilter?.search_type ?? ["thumbnail", "description"]
|
||||||
}
|
}
|
||||||
setSearchSources={(newSearchSource) =>
|
setSearchSources={(newSearchSource) =>
|
||||||
onUpdateFilter({ ...currentFilter, search_type: newSearchSource })
|
setCurrentFilter({
|
||||||
|
...currentFilter,
|
||||||
|
search_type: newSearchSource,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
{isDesktop && <DropdownMenuSeparator />}
|
{isDesktop && <DropdownMenuSeparator />}
|
||||||
<div className="flex items-center justify-evenly p-2">
|
<div className="flex items-center justify-evenly p-2">
|
||||||
<Button
|
<Button
|
||||||
@ -104,7 +127,13 @@ export default function SearchFilterDialog({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentFilter(filter ?? {});
|
setCurrentFilter((prevFilter) => ({
|
||||||
|
...prevFilter,
|
||||||
|
time_range: undefined,
|
||||||
|
zones: undefined,
|
||||||
|
sub_labels: undefined,
|
||||||
|
search_type: ["thumbnail", "description"],
|
||||||
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
@ -118,7 +147,7 @@ export default function SearchFilterDialog({
|
|||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
content={content}
|
content={content}
|
||||||
contentClassName={cn(
|
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",
|
isMobileOnly && "pb-20",
|
||||||
)}
|
)}
|
||||||
open={open}
|
open={open}
|
||||||
@ -184,8 +213,8 @@ function TimeRangeFilterContent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-hidden">
|
<div className="overflow-x-hidden">
|
||||||
<Heading as="h4">Time Range</Heading>
|
<div className="text-lg">Time Range</div>
|
||||||
<div className="my-3 flex flex-row items-center justify-center gap-2">
|
<div className="mt-3 flex flex-row items-center justify-center gap-2">
|
||||||
<Popover
|
<Popover
|
||||||
open={startOpen}
|
open={startOpen}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
@ -280,7 +309,7 @@ export function ZoneFilterContent({
|
|||||||
<>
|
<>
|
||||||
<div className="overflow-x-hidden">
|
<div className="overflow-x-hidden">
|
||||||
<DropdownMenuSeparator className="mb-3" />
|
<DropdownMenuSeparator className="mb-3" />
|
||||||
<Heading as="h4">Zones</Heading>
|
<div className="text-lg">Zones</div>
|
||||||
{allZones && (
|
{allZones && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||||
@ -301,7 +330,7 @@ export function ZoneFilterContent({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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) => (
|
{allZones.map((item) => (
|
||||||
<FilterSwitch
|
<FilterSwitch
|
||||||
key={item}
|
key={item}
|
||||||
@ -346,7 +375,7 @@ export function SubFilterContent({
|
|||||||
return (
|
return (
|
||||||
<div className="overflow-x-hidden">
|
<div className="overflow-x-hidden">
|
||||||
<DropdownMenuSeparator className="mb-3" />
|
<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">
|
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||||
<Label className="mx-2 cursor-pointer text-primary" htmlFor="allLabels">
|
<Label className="mx-2 cursor-pointer text-primary" htmlFor="allLabels">
|
||||||
All Sub Labels
|
All Sub Labels
|
||||||
@ -362,7 +391,7 @@ export function SubFilterContent({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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) => (
|
{allSubLabels.map((item) => (
|
||||||
<FilterSwitch
|
<FilterSwitch
|
||||||
key={item}
|
key={item}
|
||||||
@ -403,8 +432,8 @@ export function SearchTypeContent({
|
|||||||
<>
|
<>
|
||||||
<div className="overflow-x-hidden">
|
<div className="overflow-x-hidden">
|
||||||
<DropdownMenuSeparator className="mb-3" />
|
<DropdownMenuSeparator className="mb-3" />
|
||||||
<Heading as="h4">Search Sources</Heading>
|
<div className="text-lg">Search Sources</div>
|
||||||
<div className="my-2.5 flex flex-col gap-2.5">
|
<div className="mt-2.5 flex flex-col gap-2.5">
|
||||||
<FilterSwitch
|
<FilterSwitch
|
||||||
label="Thumbnail Image"
|
label="Thumbnail Image"
|
||||||
isChecked={searchSources?.includes("thumbnail") ?? false}
|
isChecked={searchSources?.includes("thumbnail") ?? false}
|
||||||
|
Loading…
Reference in New Issue
Block a user