mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-15 15:45:27 +03:00
Add all filters to side panel
This commit is contained in:
parent
b7ffc1ec4c
commit
3fad61df84
@ -1,5 +1,4 @@
|
||||
import { Button } from "../ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||
import useSWR from "swr";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
@ -16,18 +15,11 @@ import {
|
||||
SearchFilter,
|
||||
SearchFilters,
|
||||
SearchSource,
|
||||
DEFAULT_TIME_RANGE_AFTER,
|
||||
DEFAULT_TIME_RANGE_BEFORE,
|
||||
} from "@/types/search";
|
||||
import { DateRange } from "react-day-picker";
|
||||
import { cn } from "@/lib/utils";
|
||||
import SubFilterIcon from "../icons/SubFilterIcon";
|
||||
import { FaLocationDot } from "react-icons/fa6";
|
||||
import { MdLabel } from "react-icons/md";
|
||||
import SearchSourceIcon from "../icons/SearchSourceIcon";
|
||||
import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog";
|
||||
import { FaArrowRight, FaClock } from "react-icons/fa";
|
||||
import { useFormattedHour } from "@/hooks/use-date-utils";
|
||||
import SearchFilterDialog from "../overlay/dialog/SearchFilterDialog";
|
||||
import { CalendarRangeFilterButton } from "./CalendarFilterButton";
|
||||
|
||||
@ -185,7 +177,6 @@ export default function SearchFilterGroup({
|
||||
config={config}
|
||||
filter={filter}
|
||||
filterValues={filterValues}
|
||||
groups={groups}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
/>
|
||||
</div>
|
||||
@ -364,404 +355,3 @@ export function GeneralFilterContent({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type ZoneFilterButtonProps = {
|
||||
allZones: string[];
|
||||
selectedZones?: string[];
|
||||
updateZoneFilter: (zones: string[] | undefined) => void;
|
||||
};
|
||||
function ZoneFilterButton({
|
||||
allZones,
|
||||
selectedZones,
|
||||
updateZoneFilter,
|
||||
}: ZoneFilterButtonProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [currentZones, setCurrentZones] = useState<string[] | undefined>(
|
||||
selectedZones,
|
||||
);
|
||||
|
||||
const buttonText = useMemo(() => {
|
||||
if (isMobile) {
|
||||
return "Zones";
|
||||
}
|
||||
|
||||
if (!selectedZones || selectedZones.length == 0) {
|
||||
return "All Zones";
|
||||
}
|
||||
|
||||
if (selectedZones.length == 1) {
|
||||
return selectedZones[0];
|
||||
}
|
||||
|
||||
return `${selectedZones.length} Zones`;
|
||||
}, [selectedZones]);
|
||||
|
||||
// ui
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentZones(selectedZones);
|
||||
// only refresh when state changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedZones]);
|
||||
|
||||
const trigger = (
|
||||
<Button
|
||||
size="sm"
|
||||
variant={selectedZones?.length ? "select" : "default"}
|
||||
className="flex items-center gap-2 capitalize"
|
||||
>
|
||||
<FaLocationDot
|
||||
className={`${selectedZones?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
||||
/>
|
||||
<div
|
||||
className={`${selectedZones?.length ? "text-selected-foreground" : "text-primary"}`}
|
||||
>
|
||||
{buttonText}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
const content = (
|
||||
<ZoneFilterContent
|
||||
allZones={allZones}
|
||||
selectedZones={selectedZones}
|
||||
currentZones={currentZones}
|
||||
setCurrentZones={setCurrentZones}
|
||||
updateZoneFilter={updateZoneFilter}
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<PlatformAwareDialog
|
||||
trigger={trigger}
|
||||
content={content}
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setCurrentZones(selectedZones);
|
||||
}
|
||||
|
||||
setOpen(open);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type SubFilterButtonProps = {
|
||||
allSubLabels: string[];
|
||||
selectedSubLabels: string[] | undefined;
|
||||
updateSubLabelFilter: (labels: string[] | undefined) => void;
|
||||
};
|
||||
function SubFilterButton({
|
||||
allSubLabels,
|
||||
selectedSubLabels,
|
||||
updateSubLabelFilter,
|
||||
}: SubFilterButtonProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [currentSubLabels, setCurrentSubLabels] = useState<
|
||||
string[] | undefined
|
||||
>(selectedSubLabels);
|
||||
|
||||
const buttonText = useMemo(() => {
|
||||
if (isMobile) {
|
||||
return "Sub Labels";
|
||||
}
|
||||
|
||||
if (!selectedSubLabels || selectedSubLabels.length == 0) {
|
||||
return "All Sub Labels";
|
||||
}
|
||||
|
||||
if (selectedSubLabels.length == 1) {
|
||||
return selectedSubLabels[0];
|
||||
}
|
||||
|
||||
return `${selectedSubLabels.length} Sub Labels`;
|
||||
}, [selectedSubLabels]);
|
||||
|
||||
const trigger = (
|
||||
<Button
|
||||
size="sm"
|
||||
variant={selectedSubLabels?.length ? "select" : "default"}
|
||||
className="flex items-center gap-2 capitalize"
|
||||
>
|
||||
<SubFilterIcon
|
||||
className={`${selectedSubLabels?.length || selectedSubLabels?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
||||
/>
|
||||
<div
|
||||
className={`${selectedSubLabels?.length ? "text-selected-foreground" : "text-primary"}`}
|
||||
>
|
||||
{buttonText}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
const content = (
|
||||
<SubFilterContent
|
||||
allSubLabels={allSubLabels}
|
||||
selectedSubLabels={selectedSubLabels}
|
||||
currentSubLabels={currentSubLabels}
|
||||
setCurrentSubLabels={setCurrentSubLabels}
|
||||
updateSubLabelFilter={updateSubLabelFilter}
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<PlatformAwareDialog
|
||||
trigger={trigger}
|
||||
content={content}
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setCurrentSubLabels(selectedSubLabels);
|
||||
}
|
||||
|
||||
setOpen(open);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type SubFilterContentProps = {
|
||||
allSubLabels: string[];
|
||||
selectedSubLabels: string[] | undefined;
|
||||
currentSubLabels: string[] | undefined;
|
||||
updateSubLabelFilter: (labels: string[] | undefined) => void;
|
||||
setCurrentSubLabels: (labels: string[] | undefined) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
export function SubFilterContent({
|
||||
allSubLabels,
|
||||
selectedSubLabels,
|
||||
currentSubLabels,
|
||||
updateSubLabelFilter,
|
||||
setCurrentSubLabels,
|
||||
onClose,
|
||||
}: SubFilterContentProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||
<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
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
id="allLabels"
|
||||
checked={currentSubLabels == undefined}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setCurrentSubLabels(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
{allSubLabels.map((item) => (
|
||||
<FilterSwitch
|
||||
key={item}
|
||||
label={item.replaceAll("_", " ")}
|
||||
isChecked={currentSubLabels?.includes(item) ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
const updatedLabels = currentSubLabels
|
||||
? [...currentSubLabels]
|
||||
: [];
|
||||
|
||||
updatedLabels.push(item);
|
||||
setCurrentSubLabels(updatedLabels);
|
||||
} else {
|
||||
const updatedLabels = currentSubLabels
|
||||
? [...currentSubLabels]
|
||||
: [];
|
||||
|
||||
// can not deselect the last item
|
||||
if (updatedLabels.length > 1) {
|
||||
updatedLabels.splice(updatedLabels.indexOf(item), 1);
|
||||
setCurrentSubLabels(updatedLabels);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{isDesktop && <DropdownMenuSeparator />}
|
||||
<div className="flex items-center justify-evenly p-2">
|
||||
<Button
|
||||
variant="select"
|
||||
onClick={() => {
|
||||
if (selectedSubLabels != currentSubLabels) {
|
||||
updateSubLabelFilter(currentSubLabels);
|
||||
}
|
||||
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateSubLabelFilter(undefined);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type SearchTypeButtonProps = {
|
||||
selectedSearchSources: SearchSource[] | undefined;
|
||||
updateSearchSourceFilter: (sources: SearchSource[] | undefined) => void;
|
||||
};
|
||||
function SearchTypeButton({
|
||||
selectedSearchSources,
|
||||
updateSearchSourceFilter,
|
||||
}: SearchTypeButtonProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const buttonText = useMemo(() => {
|
||||
if (isMobile) {
|
||||
return "Sources";
|
||||
}
|
||||
|
||||
if (
|
||||
!selectedSearchSources ||
|
||||
selectedSearchSources.length == 0 ||
|
||||
selectedSearchSources.length == 2
|
||||
) {
|
||||
return "All Search Sources";
|
||||
}
|
||||
|
||||
if (selectedSearchSources.length == 1) {
|
||||
return selectedSearchSources[0];
|
||||
}
|
||||
|
||||
return `${selectedSearchSources.length} Search Sources`;
|
||||
}, [selectedSearchSources]);
|
||||
|
||||
const trigger = (
|
||||
<Button
|
||||
size="sm"
|
||||
variant={selectedSearchSources?.length != 2 ? "select" : "default"}
|
||||
className="flex items-center gap-2 capitalize"
|
||||
>
|
||||
<SearchSourceIcon
|
||||
className={`${selectedSearchSources?.length != 2 ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
||||
/>
|
||||
<div
|
||||
className={`${selectedSearchSources?.length != 2 ? "text-selected-foreground" : "text-primary"}`}
|
||||
>
|
||||
{buttonText}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
const content = (
|
||||
<SearchTypeContent
|
||||
selectedSearchSources={selectedSearchSources}
|
||||
updateSearchSourceFilter={updateSearchSourceFilter}
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<PlatformAwareDialog
|
||||
trigger={trigger}
|
||||
content={content}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type SearchTypeContentProps = {
|
||||
selectedSearchSources: SearchSource[] | undefined;
|
||||
updateSearchSourceFilter: (sources: SearchSource[] | undefined) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
export function SearchTypeContent({
|
||||
selectedSearchSources,
|
||||
updateSearchSourceFilter,
|
||||
onClose,
|
||||
}: SearchTypeContentProps) {
|
||||
const [currentSearchSources, setCurrentSearchSources] = useState<
|
||||
SearchSource[] | undefined
|
||||
>(selectedSearchSources);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
<FilterSwitch
|
||||
label="Thumbnail Image"
|
||||
isChecked={currentSearchSources?.includes("thumbnail") ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedSources = currentSearchSources
|
||||
? [...currentSearchSources]
|
||||
: [];
|
||||
|
||||
if (isChecked) {
|
||||
updatedSources.push("thumbnail");
|
||||
setCurrentSearchSources(updatedSources);
|
||||
} else {
|
||||
if (updatedSources.length > 1) {
|
||||
const index = updatedSources.indexOf("thumbnail");
|
||||
if (index !== -1) updatedSources.splice(index, 1);
|
||||
setCurrentSearchSources(updatedSources);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FilterSwitch
|
||||
label="Description"
|
||||
isChecked={currentSearchSources?.includes("description") ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedSources = currentSearchSources
|
||||
? [...currentSearchSources]
|
||||
: [];
|
||||
|
||||
if (isChecked) {
|
||||
updatedSources.push("description");
|
||||
setCurrentSearchSources(updatedSources);
|
||||
} else {
|
||||
if (updatedSources.length > 1) {
|
||||
const index = updatedSources.indexOf("description");
|
||||
if (index !== -1) updatedSources.splice(index, 1);
|
||||
setCurrentSearchSources(updatedSources);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{isDesktop && <DropdownMenuSeparator />}
|
||||
<div className="flex items-center justify-evenly p-2">
|
||||
<Button
|
||||
variant="select"
|
||||
onClick={() => {
|
||||
if (selectedSearchSources != currentSearchSources) {
|
||||
updateSearchSourceFilter(currentSearchSources);
|
||||
}
|
||||
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateSearchSourceFilter(undefined);
|
||||
setCurrentSearchSources(["thumbnail", "description"]);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
SearchFilter,
|
||||
SearchSource,
|
||||
} from "@/types/search";
|
||||
import { CameraGroupConfig, FrigateConfig } from "@/types/frigateConfig";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@ -33,14 +33,12 @@ type SearchFilterDialogProps = {
|
||||
zones: string[];
|
||||
search_type: SearchSource[];
|
||||
};
|
||||
groups: [string, CameraGroupConfig][];
|
||||
onUpdateFilter: (filter: SearchFilter) => void;
|
||||
};
|
||||
export default function SearchFilterDialog({
|
||||
config,
|
||||
filter,
|
||||
filterValues,
|
||||
groups,
|
||||
onUpdateFilter,
|
||||
}: SearchFilterDialogProps) {
|
||||
// data
|
||||
@ -50,6 +48,8 @@ export default function SearchFilterDialog({
|
||||
|
||||
// state
|
||||
|
||||
console.log(`the filter is ${JSON.stringify(currentFilter)}`);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const trigger = (
|
||||
@ -64,14 +64,29 @@ export default function SearchFilterDialog({
|
||||
config={config}
|
||||
timeRange={currentFilter.time_range}
|
||||
updateTimeRange={(newRange) =>
|
||||
setCurrentFilter({ time_range: newRange, ...currentFilter })
|
||||
setCurrentFilter({ ...currentFilter, time_range: newRange })
|
||||
}
|
||||
/>
|
||||
<ZoneFilterContent
|
||||
allZones={filterValues.zones}
|
||||
zones={currentFilter.zones}
|
||||
updateZones={(newZones) =>
|
||||
setCurrentFilter({ zones: newZones, ...currentFilter })
|
||||
setCurrentFilter({ ...currentFilter, zones: newZones })
|
||||
}
|
||||
/>
|
||||
<SubFilterContent
|
||||
allSubLabels={allSubLabels}
|
||||
subLabels={currentFilter.sub_labels}
|
||||
setSubLabels={(newSubLabels) =>
|
||||
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
||||
}
|
||||
/>
|
||||
<SearchTypeContent
|
||||
searchSources={
|
||||
currentFilter?.search_type ?? ["thumbnail", "description"]
|
||||
}
|
||||
setSearchSources={(newSearchSource) =>
|
||||
onUpdateFilter({ ...currentFilter, search_type: newSearchSource })
|
||||
}
|
||||
/>
|
||||
{isDesktop && <DropdownMenuSeparator />}
|
||||
@ -105,7 +120,13 @@ export default function SearchFilterDialog({
|
||||
content={content}
|
||||
contentClassName="w-auto lg:w-[300px]"
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setCurrentFilter(filter ?? {});
|
||||
}
|
||||
|
||||
setOpen(open);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -256,9 +277,10 @@ export function ZoneFilterContent({
|
||||
return (
|
||||
<>
|
||||
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||
{isDesktop && <DropdownMenuSeparator className="mb-2" />}
|
||||
<Heading as="h4">Zones</Heading>
|
||||
{allZones && (
|
||||
<>
|
||||
{isDesktop && <DropdownMenuSeparator />}
|
||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||
<Label
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
@ -309,58 +331,116 @@ export function ZoneFilterContent({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {filters.includes("date") && (
|
||||
<CalendarRangeFilterButton
|
||||
range={
|
||||
filter?.after == undefined || filter?.before == undefined
|
||||
? undefined
|
||||
: {
|
||||
from: new Date(filter.after * 1000),
|
||||
to: new Date(filter.before * 1000),
|
||||
type SubFilterContentProps = {
|
||||
allSubLabels: string[];
|
||||
subLabels: string[] | undefined;
|
||||
setSubLabels: (labels: string[] | undefined) => void;
|
||||
};
|
||||
export function SubFilterContent({
|
||||
allSubLabels,
|
||||
subLabels,
|
||||
setSubLabels,
|
||||
}: SubFilterContentProps) {
|
||||
return (
|
||||
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||
{isDesktop && <DropdownMenuSeparator className="mb-2" />}
|
||||
<Heading as="h4">Sub Labels</Heading>
|
||||
<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
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
id="allLabels"
|
||||
checked={subLabels == undefined}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setSubLabels(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
{allSubLabels.map((item) => (
|
||||
<FilterSwitch
|
||||
key={item}
|
||||
label={item.replaceAll("_", " ")}
|
||||
isChecked={subLabels?.includes(item) ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
const updatedLabels = subLabels ? [...subLabels] : [];
|
||||
|
||||
updatedLabels.push(item);
|
||||
setSubLabels(updatedLabels);
|
||||
} else {
|
||||
const updatedLabels = subLabels ? [...subLabels] : [];
|
||||
|
||||
// can not deselect the last item
|
||||
if (updatedLabels.length > 1) {
|
||||
updatedLabels.splice(updatedLabels.indexOf(item), 1);
|
||||
setSubLabels(updatedLabels);
|
||||
}
|
||||
}
|
||||
defaultText={isMobile ? "Dates" : "All Dates"}
|
||||
updateSelectedRange={onUpdateSelectedRange}
|
||||
/>
|
||||
)}
|
||||
{filters.includes("time") && (
|
||||
<TimeRangeFilterButton
|
||||
config={config}
|
||||
timeRange={filter?.time_range}
|
||||
updateTimeRange={(time_range) =>
|
||||
onUpdateFilter({ ...filter, time_range })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{filters.includes("zone") && allZones.length > 0 && (
|
||||
<ZoneFilterButton
|
||||
allZones={filterValues.zones}
|
||||
selectedZones={filter?.zones}
|
||||
updateZoneFilter={(newZones) =>
|
||||
onUpdateFilter({ ...filter, zones: newZones })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{filters.includes("sub") && (
|
||||
<SubFilterButton
|
||||
allSubLabels={allSubLabels}
|
||||
selectedSubLabels={filter?.sub_labels}
|
||||
updateSubLabelFilter={(newSubLabels) =>
|
||||
onUpdateFilter({ ...filter, sub_labels: newSubLabels })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{config?.semantic_search?.enabled &&
|
||||
filters.includes("source") &&
|
||||
!filter?.search_type?.includes("similarity") && (
|
||||
<SearchTypeButton
|
||||
selectedSearchSources={
|
||||
filter?.search_type ?? ["thumbnail", "description"]
|
||||
}
|
||||
updateSearchSourceFilter={(newSearchSource) =>
|
||||
onUpdateFilter({ ...filter, search_type: newSearchSource })
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
*/
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type SearchTypeContentProps = {
|
||||
searchSources: SearchSource[] | undefined;
|
||||
setSearchSources: (sources: SearchSource[] | undefined) => void;
|
||||
};
|
||||
export function SearchTypeContent({
|
||||
searchSources,
|
||||
setSearchSources,
|
||||
}: SearchTypeContentProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||
{isDesktop && <DropdownMenuSeparator className="mb-2" />}
|
||||
<Heading as="h4">Search Sources</Heading>
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
<FilterSwitch
|
||||
label="Thumbnail Image"
|
||||
isChecked={searchSources?.includes("thumbnail") ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedSources = searchSources ? [...searchSources] : [];
|
||||
|
||||
if (isChecked) {
|
||||
updatedSources.push("thumbnail");
|
||||
setSearchSources(updatedSources);
|
||||
} else {
|
||||
if (updatedSources.length > 1) {
|
||||
const index = updatedSources.indexOf("thumbnail");
|
||||
if (index !== -1) updatedSources.splice(index, 1);
|
||||
setSearchSources(updatedSources);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FilterSwitch
|
||||
label="Description"
|
||||
isChecked={searchSources?.includes("description") ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedSources = searchSources ? [...searchSources] : [];
|
||||
|
||||
if (isChecked) {
|
||||
updatedSources.push("description");
|
||||
setSearchSources(updatedSources);
|
||||
} else {
|
||||
if (updatedSources.length > 1) {
|
||||
const index = updatedSources.indexOf("description");
|
||||
if (index !== -1) updatedSources.splice(index, 1);
|
||||
setSearchSources(updatedSources);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user