import { useEffect, useMemo } from "react"; import { isIOS, isMobileOnly, isSafari } from "react-device-detect"; import useSWR from "swr"; import { useApiHost } from "@/api"; import { cn } from "@/lib/utils"; import { LuArrowRightCircle } from "react-icons/lu"; import { useNavigate } from "react-router-dom"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { TooltipPortal } from "@radix-ui/react-tooltip"; import { SearchResult } from "@/types/search"; import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator"; import useImageLoaded from "@/hooks/use-image-loaded"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import { useEventUpdate } from "@/api/ws"; import { isEqual } from "lodash"; type ExploreViewProps = { searchDetail: SearchResult | undefined; setSearchDetail: (search: SearchResult | undefined) => void; }; export default function ExploreView({ searchDetail, setSearchDetail, }: ExploreViewProps) { // title useEffect(() => { document.title = "Explore - Frigate"; }, []); // data const { data: events, mutate } = useSWR( [ "events/explore", { limit: isMobileOnly ? 5 : 10, }, ], { revalidateOnFocus: true, }, ); const eventsByLabel = useMemo(() => { if (!events) return {}; return events.reduce>((acc, event) => { const label = event.label || "Unknown"; if (!acc[label]) { acc[label] = []; } acc[label].push(event); return acc; }, {}); }, [events]); const eventUpdate = useEventUpdate(); useEffect(() => { mutate(); // mutate / revalidate when event description updates come in // eslint-disable-next-line react-hooks/exhaustive-deps }, [eventUpdate]); // update search detail when results change useEffect(() => { if (searchDetail && events) { const updatedSearchDetail = events.find( (result) => result.id === searchDetail.id, ); if (updatedSearchDetail && !isEqual(updatedSearchDetail, searchDetail)) { setSearchDetail(updatedSearchDetail); } } }, [events, searchDetail, setSearchDetail]); if (!events) { return ( ); } return (
{Object.entries(eventsByLabel).map(([label, filteredEvents]) => ( ))}
); } type ThumbnailRowType = { objectType: string; searchResults?: SearchResult[]; setSearchDetail: (search: SearchResult | undefined) => void; }; function ThumbnailRow({ objectType, searchResults, setSearchDetail, }: ThumbnailRowType) { const navigate = useNavigate(); const handleSearch = (label: string) => { const similaritySearchParams = new URLSearchParams({ labels: label, }).toString(); navigate(`/explore?${similaritySearchParams}`); }; return (
{objectType.replaceAll("_", " ")} {searchResults && ( ( { // @ts-expect-error we know this is correct searchResults[0].event_count }{" "} tracked objects){" "} )}
{searchResults?.map((event) => (
))}
handleSearch(objectType)} >
); } type ExploreThumbnailImageProps = { event: SearchResult; setSearchDetail: (search: SearchResult | undefined) => void; }; function ExploreThumbnailImage({ event, setSearchDetail, }: ExploreThumbnailImageProps) { const apiHost = useApiHost(); const [imgRef, imgLoaded, onImgLoad] = useImageLoaded(); return ( <> setSearchDetail(event)} onLoad={() => { onImgLoad(); }} /> ); } function ExploreMoreLink({ objectType }: { objectType: string }) { const formattedType = objectType.replaceAll("_", " "); const label = formattedType.endsWith("s") ? `${formattedType}es` : `${formattedType}s`; return
Explore More {label}
; }