import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useApiHost } from "@/api"; import { getIconForLabel } from "@/utils/iconUtil"; import TimeAgo from "../dynamic/TimeAgo"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { isIOS, isMobile, isSafari } from "react-device-detect"; import Chip from "@/components/indicators/Chip"; import { useFormattedTimestamp } from "@/hooks/use-date-utils"; import useImageLoaded from "@/hooks/use-image-loaded"; import { useSwipeable } from "react-swipeable"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator"; import ActivityIndicator from "../indicators/activity-indicator"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { SearchResult } from "@/types/search"; import useContextMenu from "@/hooks/use-contextmenu"; import { cn } from "@/lib/utils"; type SearchThumbnailProps = { searchResult: SearchResult; scrollLock?: boolean; findSimilar: () => void; onClick: (searchResult: SearchResult, detail: boolean) => void; }; export default function SearchThumbnail({ searchResult, scrollLock = false, findSimilar, onClick, }: SearchThumbnailProps) { const apiHost = useApiHost(); const { data: config } = useSWR("config"); const [imgRef, imgLoaded, onImgLoad] = useImageLoaded(); // interaction const swipeHandlers = useSwipeable({ onSwipedLeft: () => setDetails(false), onSwipedRight: () => setDetails(true), preventScrollOnSwipe: true, }); useContextMenu(imgRef, findSimilar); // Hover Details const [hoverTimeout, setHoverTimeout] = useState(); const [details, setDetails] = useState(false); const [tooltipHovering, setTooltipHovering] = useState(false); const showingMoreDetail = useMemo( () => details && !tooltipHovering, [details, tooltipHovering], ); const [isHovered, setIsHovered] = useState(false); const handleOnClick = useCallback( (e: React.MouseEvent) => { if (!showingMoreDetail) { onClick(searchResult, e.metaKey); } }, [searchResult, showingMoreDetail, onClick], ); useEffect(() => { if (isHovered && scrollLock) { return; } if (isHovered && !tooltipHovering) { setHoverTimeout( setTimeout(() => { setDetails(true); setHoverTimeout(null); }, 500), ); } else { if (hoverTimeout) { clearTimeout(hoverTimeout); } setDetails(false); } // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps }, [isHovered, scrollLock, tooltipHovering]); // date const formattedDate = useFormattedTimestamp( searchResult.start_time, config?.ui.time_format == "24hour" ? "%b %-d, %H:%M" : "%b %-d, %I:%M %p", ); return (
setIsHovered(true)} onMouseLeave={isMobile ? undefined : () => setIsHovered(false)} onClick={handleOnClick} {...swipeHandlers} >
{ onImgLoad(); }} />
setTooltipHovering(true)} onMouseLeave={() => setTooltipHovering(false)} >
{ <> onClick(searchResult, true)} > {getIconForLabel( searchResult.label, "size-3 text-white", )} }
{[...new Set([searchResult.label])] .filter( (item) => item !== undefined && !item.includes("-verified"), ) .map((text) => capitalizeFirstLetter(text)) .sort() .join(", ") .replaceAll("-verified", "")}
{searchResult.end_time ? ( ) : (
)} {formattedDate}
); }