import TimeAgo from "../dynamic/TimeAgo"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { useCallback, useEffect, useMemo, useState } from "react"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { REVIEW_PADDING, ReviewSegment } from "@/types/review"; import { useNavigate } from "react-router-dom"; import { RecordingStartingPoint } from "@/types/record"; import axios from "axios"; import { isCurrentHour } from "@/utils/dateUtil"; import { useCameraPreviews } from "@/hooks/use-camera-previews"; import { baseUrl } from "@/api/baseUrl"; import { VideoPreview } from "../preview/ScrubbablePreview"; import { useApiHost } from "@/api"; import { isDesktop, isSafari } from "react-device-detect"; import { usePersistence } from "@/hooks/use-persistence"; import { Skeleton } from "../ui/skeleton"; import { Button } from "../ui/button"; import { FaCircleCheck } from "react-icons/fa6"; import { cn } from "@/lib/utils"; import { useTranslation } from "react-i18next"; type AnimatedEventCardProps = { event: ReviewSegment; selectedGroup?: string; updateEvents: () => void; }; export function AnimatedEventCard({ event, selectedGroup, updateEvents, }: AnimatedEventCardProps) { const { t } = useTranslation(["views/events"]); const { data: config } = useSWR("config"); const apiHost = useApiHost(); const currentHour = useMemo(() => isCurrentHour(event.start_time), [event]); const initialTimeRange = useMemo(() => { return { after: Math.round(event.start_time), before: Math.round(event.end_time || event.start_time + 20), }; }, [event]); // preview const previews = useCameraPreviews(initialTimeRange, { camera: event.camera, fetchPreviews: !currentHour, }); const tooltipText = useMemo(() => { if (event?.data?.metadata?.title) { return event.data.metadata.title; } return ( `${[ ...new Set([ ...(event.data.objects || []), ...(event.data.sub_labels || []), ...(event.data.audio || []), ]), ] .filter((item) => item !== undefined && !item.includes("-verified")) .map((text) => text.charAt(0).toUpperCase() + text.substring(1)) .sort() .join(", ") .replaceAll("-verified", "")} ` + t("detected") ); }, [event, t]); // visibility const [windowVisible, setWindowVisible] = useState(true); const visibilityListener = useCallback(() => { setWindowVisible(document.visibilityState == "visible"); }, []); useEffect(() => { addEventListener("visibilitychange", visibilityListener); return () => { removeEventListener("visibilitychange", visibilityListener); }; }, [visibilityListener]); const [isLoaded, setIsLoaded] = useState(false); const [isHovered, setIsHovered] = useState(false); // interaction const navigate = useNavigate(); const onOpenReview = useCallback(() => { const url = selectedGroup && selectedGroup != "default" ? `review?group=${selectedGroup}` : "review"; navigate(url, { state: { severity: event.severity, recording: { camera: event.camera, startTime: event.start_time - REVIEW_PADDING, severity: event.severity, } as RecordingStartingPoint, }, }); axios.post(`reviews/viewed`, { ids: [event.id] }); }, [navigate, selectedGroup, event]); // image behavior const [alertVideos, _, alertVideosLoaded] = usePersistence( "alertVideos", true, ); const aspectRatio = useMemo(() => { if ( !config || !alertVideos || !Object.keys(config.cameras).includes(event.camera) ) { return 16 / 9; } const detect = config.cameras[event.camera].detect; return detect.width / detect.height; }, [alertVideos, config, event]); return (
setIsHovered(true) : undefined} onMouseLeave={isDesktop ? () => setIsHovered(false) : undefined} > {isHovered && ( {t("markAsReviewed")} )} {previews != undefined && alertVideosLoaded && (
{ if (e.button === 1) { window .open(`${baseUrl}review?id=${event.id}`, "_blank") ?.focus(); } }} > {!alertVideos ? ( setIsLoaded(true)} /> ) : ( <> {previews.length ? ( {}} setIgnoreClick={() => {}} isPlayingBack={() => {}} onTimeUpdate={() => { if (!isLoaded) { setIsLoaded(true); } }} windowVisible={windowVisible} /> ) : ( )} )}
)} {isLoaded && (
)} {!isLoaded && ( )}
{tooltipText}
); }