From b222ad31e04b58f06141e59f51194fa1c6bcc034 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 1 Nov 2025 09:04:34 -0500 Subject: [PATCH] remove --- .../overlay/detail/TrackingDetails.tsx.bak | 1023 ----------------- 1 file changed, 1023 deletions(-) delete mode 100644 web/src/components/overlay/detail/TrackingDetails.tsx.bak diff --git a/web/src/components/overlay/detail/TrackingDetails.tsx.bak b/web/src/components/overlay/detail/TrackingDetails.tsx.bak deleted file mode 100644 index 06d0f5017..000000000 --- a/web/src/components/overlay/detail/TrackingDetails.tsx.bak +++ /dev/null @@ -1,1023 +0,0 @@ -import useSWR from "swr"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { Event } from "@/types/event"; -import ActivityIndicator from "@/components/indicators/activity-indicator"; -import { Button } from "@/components/ui/button"; -import { TrackingDetailsSequence } from "@/types/timeline"; -import Heading from "@/components/ui/heading"; -import { FrigateConfig } from "@/types/frigateConfig"; -import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; -import { getIconForLabel } from "@/utils/iconUtil"; -import { - LuCircle, - LuCircleDot, - LuEar, - LuPlay, - LuSettings, - LuTruck, -} from "react-icons/lu"; -import { IoMdExit } from "react-icons/io"; -import { - MdFaceUnlock, - MdOutlineLocationOn, - MdOutlinePictureInPictureAlt, -} from "react-icons/md"; -import { cn } from "@/lib/utils"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { AnnotationSettingsPane } from "./AnnotationSettingsPane"; -import { TooltipPortal } from "@radix-ui/react-tooltip"; -import HlsVideoPlayer from "@/components/player/HlsVideoPlayer"; -import { baseUrl } from "@/api/baseUrl"; -import { REVIEW_PADDING } from "@/types/review"; -import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record"; -import { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuPortal, -} from "@/components/ui/dropdown-menu"; -import { Link, useNavigate } from "react-router-dom"; -import { getLifecycleItemDescription } from "@/utils/lifecycleUtil"; -import { IoPlayCircleOutline } from "react-icons/io5"; -import { useTranslation } from "react-i18next"; -import { getTranslatedLabel } from "@/utils/i18n"; -import { Badge } from "@/components/ui/badge"; -import { HiDotsHorizontal } from "react-icons/hi"; -import axios from "axios"; -import { toast } from "sonner"; -import { DetailStreamProvider, useDetailStream } from "@/context/detail-stream-context"; - -type TrackingDetailsProps = { - className?: string; - event: Event; - fullscreen?: boolean; - showImage?: boolean; - showLifecycle?: boolean; - timeIndex?: number; - setTimeIndex?: (index: number) => void; -}; - -// Wrapper component that provides DetailStreamContext -export default function TrackingDetails(props: TrackingDetailsProps) { - const [currentTime, setCurrentTime] = useState(props.event.start_time ?? 0); - - return ( - - - - ); -} - -// Inner component with access to DetailStreamContext -function TrackingDetailsInner({ - className, - event, - showImage = true, - showLifecycle = false, - timeIndex: propTimeIndex, - setTimeIndex: propSetTimeIndex, - onTimeUpdate, -}: TrackingDetailsProps & { onTimeUpdate: (time: number) => void }) { - const { t } = useTranslation(["views/explore"]); - const { setSelectedObjectIds, annotationOffset: contextAnnotationOffset, setAnnotationOffset: setContextAnnotationOffset } = useDetailStream(); - - const { data: eventSequence } = useSWR([ - "timeline", - { - source_id: event.id, - }, - ]); - - const { data: config } = useSWR("config"); - - const videoRef = useRef(null); - const containerRef = useRef(null); - const [selectedZone, setSelectedZone] = useState(""); - const [lifecycleZones, setLifecycleZones] = useState([]); - const [showControls, setShowControls] = useState(false); - const [showZones, setShowZones] = useState(true); - - const aspectRatio = useMemo(() => { - if (!config) { - return 16 / 9; - } - - return ( - config.cameras[event.camera].detect.width / - config.cameras[event.camera].detect.height - ); - }, [config, event]); - - const label = event.sub_label - ? event.sub_label - : getTranslatedLabel(event.label); - - const getZoneColor = useCallback( - (zoneName: string) => { - const zoneColor = - config?.cameras?.[event.camera]?.zones?.[zoneName]?.color; - if (zoneColor) { - const reversed = [...zoneColor].reverse(); - return reversed; - } - }, - [config, event], - ); - - const getObjectColor = useCallback( - (label: string) => { - const objectColor = config?.model?.colormap[label]; - if (objectColor) { - const reversed = [...objectColor].reverse(); - return reversed; - } - }, - [config], - ); - - const getZonePolygon = useCallback( - (zoneName: string) => { - if (!videoRef.current || !config) { - return; - } - const zonePoints = - config?.cameras[event.camera].zones[zoneName].coordinates; - const videoElement = videoRef.current; - const videoWidth = videoElement.videoWidth; - const videoHeight = videoElement.videoHeight; - - if (!videoWidth || !videoHeight) { - return; - } - - return zonePoints - .split(",") - .map(Number.parseFloat) - .reduce((acc, value, index) => { - const isXCoordinate = index % 2 === 0; - const coordinate = isXCoordinate - ? value * videoWidth - : value * videoHeight; - acc.push(coordinate); - return acc; - }, [] as number[]) - .join(","); - }, - [config, event], - ); - - const [boxStyle, setBoxStyle] = useState(null); - const [attributeBoxStyle, setAttributeBoxStyle] = - useState(null); - - const configAnnotationOffset = useMemo(() => { - if (!config) { - return 0; - } - - return config.cameras[event.camera]?.detect?.annotation_offset || 0; - }, [config, event]); - - const [annotationOffset, setAnnotationOffset] = useState( - configAnnotationOffset, - ); - - const savedPathPoints = useMemo(() => { - return ( - event.data.path_data?.map(([coords, timestamp]: [number[], number]) => ({ - x: coords[0], - y: coords[1], - timestamp, - lifecycle_item: undefined, - })) || [] - ); - }, [event.data.path_data]); - - const eventSequencePoints = useMemo(() => { - return ( - eventSequence - ?.filter((event) => event.data.box !== undefined) - .map((event) => { - const [left, top, width, height] = event.data.box!; - - return { - x: left + width / 2, // Center x-coordinate - y: top + height, // Bottom y-coordinate - timestamp: event.timestamp, - lifecycle_item: event, - }; - }) || [] - ); - }, [eventSequence]); - - // final object path with timeline points included - const pathPoints = useMemo(() => { - // don't display a path if we don't have any saved path points - if ( - savedPathPoints.length === 0 || - config?.cameras[event.camera]?.onvif.autotracking.enabled_in_config - ) - return []; - return [...savedPathPoints, ...eventSequencePoints].sort( - (a, b) => a.timestamp - b.timestamp, - ); - }, [savedPathPoints, eventSequencePoints, config, event]); - - const [localTimeIndex, setLocalTimeIndex] = useState(0); - - const timeIndex = - propTimeIndex !== undefined ? propTimeIndex : localTimeIndex; - const setTimeIndex = propSetTimeIndex || setLocalTimeIndex; - - const handleSetBox = useCallback( - (box: number[], attrBox: number[] | undefined) => { - if (videoRef.current && Array.isArray(box) && box.length === 4) { - const videoElement = videoRef.current; - const videoWidth = videoElement.videoWidth; - const videoHeight = videoElement.videoHeight; - - if (!videoWidth || !videoHeight || !displayWidth || !displayHeight) { - return; - } - - const style = { - left: `${box[0] * displayWidth}px`, - top: `${box[1] * displayHeight}px`, - width: `${box[2] * displayWidth}px`, - height: `${box[3] * displayHeight}px`, - borderColor: `rgb(${getObjectColor(event.label)?.join(",")})`, - }; - - if (attrBox) { - const attrStyle = { - left: `${attrBox[0] * displayWidth}px`, - top: `${attrBox[1] * displayHeight}px`, - width: `${attrBox[2] * displayWidth}px`, - height: `${attrBox[3] * displayHeight}px`, - borderColor: `rgb(${getObjectColor(event.label)?.join(",")})`, - }; - setAttributeBoxStyle(attrStyle); - } else { - setAttributeBoxStyle(null); - } - - setBoxStyle(style); - } - }, - [event, getObjectColor, displayWidth, displayHeight], - ); - - // carousels - - // Selected lifecycle item index; -1 when viewing a path-only point - - const handlePathPointClick = useCallback( - (index: number) => { - if (!eventSequence) return; - const sequenceIndex = eventSequence.findIndex( - (item) => item.timestamp === pathPoints[index].timestamp, - ); - if (sequenceIndex !== -1) { - setTimeIndex(eventSequence[sequenceIndex].timestamp); - handleSetBox( - eventSequence[sequenceIndex]?.data.box ?? [], - eventSequence[sequenceIndex]?.data?.attribute_box, - ); - setLifecycleZones(eventSequence[sequenceIndex]?.data.zones); - } else { - // click on a normal path point, not a lifecycle point - setTimeIndex(pathPoints[index].timestamp); - setBoxStyle(null); - setLifecycleZones([]); - } - }, - [eventSequence, pathPoints, handleSetBox, setTimeIndex], - ); - - const formattedStart = config - ? formatUnixTimestampToDateTime(event.start_time ?? 0, { - timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t("time.formattedTimestamp.24hour", { - ns: "common", - }) - : t("time.formattedTimestamp.12hour", { - ns: "common", - }), - time_style: "medium", - date_style: "medium", - }) - : ""; - - const formattedEnd = config - ? formatUnixTimestampToDateTime(event.end_time ?? 0, { - timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t("time.formattedTimestamp.24hour", { - ns: "common", - }) - : t("time.formattedTimestamp.12hour", { - ns: "common", - }), - time_style: "medium", - date_style: "medium", - }) - : ""; - - useEffect(() => { - if (!eventSequence || eventSequence.length === 0) return; - // If timeIndex hasn't been set to a non-zero value, prefer the first lifecycle timestamp - if (timeIndex == null || timeIndex === 0) { - setTimeIndex(eventSequence[0].timestamp); - handleSetBox( - eventSequence[0]?.data.box ?? [], - eventSequence[0]?.data?.attribute_box, - ); - setLifecycleZones(eventSequence[0]?.data.zones); - } - }, [eventSequence, timeIndex, handleSetBox, setTimeIndex]); - - // When timeIndex changes, sync the box/zones to matching lifecycle, else clear - useEffect(() => { - if (!eventSequence || propTimeIndex == null) return; - const idx = eventSequence.findIndex((i) => i.timestamp === propTimeIndex); - if (idx !== -1) { - handleSetBox( - eventSequence[idx]?.data.box ?? [], - eventSequence[idx]?.data?.attribute_box, - ); - setLifecycleZones(eventSequence[idx]?.data.zones); - } else { - // Non-lifecycle point (e.g., saved path point) - setBoxStyle(null); - setLifecycleZones([]); - } - }, [propTimeIndex, eventSequence, handleSetBox]); - - const selectedIndex = useMemo(() => { - if (!eventSequence || eventSequence.length === 0) return 0; - const idx = eventSequence.findIndex((i) => i.timestamp === timeIndex); - return idx === -1 ? 0 : idx; - }, [eventSequence, timeIndex]); - - // Calculate how far down the blue line should extend based on timeIndex - const calculateLineHeight = () => { - if (!eventSequence || eventSequence.length === 0) return 0; - - const currentTime = timeIndex ?? 0; - - // Find which events have been passed - let lastPassedIndex = -1; - for (let i = 0; i < eventSequence.length; i++) { - if (currentTime >= (eventSequence[i].timestamp ?? 0)) { - lastPassedIndex = i; - } else { - break; - } - } - - // No events passed yet - if (lastPassedIndex < 0) return 0; - - // All events passed - if (lastPassedIndex >= eventSequence.length - 1) return 100; - - // Calculate percentage based on item position, not time - // Each item occupies an equal visual space regardless of time gaps - const itemPercentage = 100 / (eventSequence.length - 1); - - // Find progress between current and next event for smooth transition - const currentEvent = eventSequence[lastPassedIndex]; - const nextEvent = eventSequence[lastPassedIndex + 1]; - const currentTimestamp = currentEvent.timestamp ?? 0; - const nextTimestamp = nextEvent.timestamp ?? 0; - - // Calculate interpolation between the two events - const timeBetween = nextTimestamp - currentTimestamp; - const timeElapsed = currentTime - currentTimestamp; - const interpolation = timeBetween > 0 ? timeElapsed / timeBetween : 0; - - // Base position plus interpolated progress to next item - return Math.min( - 100, - lastPassedIndex * itemPercentage + interpolation * itemPercentage, - ); - }; - - const blueLineHeight = calculateLineHeight(); - - const videoSource = useMemo(() => { - const startTime = event.start_time - REVIEW_PADDING; - const endTime = (event.end_time ?? Date.now() / 1000) + REVIEW_PADDING; - return `${baseUrl}vod/${event.camera}/start/${startTime}/end/${endTime}/index.m3u8`; - }, [event]); - - // Determine camera aspect ratio category - const cameraAspect = useMemo(() => { - if (!aspectRatio) { - return "normal"; - } else if (aspectRatio > ASPECT_WIDE_LAYOUT) { - return "wide"; - } else if (aspectRatio < ASPECT_VERTICAL_LAYOUT) { - return "tall"; - } else { - return "normal"; - } - }, [aspectRatio]); - - // Apply appropriate classes based on aspect ratio - const containerClasses = useMemo(() => { - if (cameraAspect === "wide") { - return "w-full aspect-wide overflow-hidden"; - } else if (cameraAspect === "tall") { - return "h-full aspect-tall overflow-hidden"; - } else { - return "w-full aspect-video overflow-hidden"; - } - }, [cameraAspect]); - - // Check if video metadata has loaded - const [videoReady, setVideoReady] = useState(false); - - useEffect(() => { - const checkVideoReady = () => { - const isReady = - videoRef.current?.readyState !== undefined && - videoRef.current.readyState >= HTMLMediaElement.HAVE_METADATA; - setVideoReady(isReady); - - if (videoRef.current) { - console.log( - "Video dimensions:", - videoRef.current.clientWidth, - videoRef.current.clientHeight, - ); - setDisplayWidth(videoRef.current.clientWidth); - setDisplayHeight(videoRef.current.clientHeight); - } - }; - - const video = videoRef.current; - if (video) { - // Check immediately - checkVideoReady(); - - // Listen for metadata load - video.addEventListener("loadedmetadata", checkVideoReady); - - return () => { - video.removeEventListener("loadedmetadata", checkVideoReady); - }; - } - }, [videoRef.current]); - - // Seek video to specific time (relative to event start) - useEffect(() => { - if (videoRef.current && propTimeIndex !== undefined) { - const relativeTime = - propTimeIndex - - event.start_time + - REVIEW_PADDING + - annotationOffset / 1000; - if (relativeTime >= 0) { - videoRef.current.currentTime = relativeTime; - } - } - }, [propTimeIndex, event.start_time, annotationOffset]); - - if (!config) { - return ; - } - - return ( -
- - - {showImage && ( -
- -
- {showZones && - videoRef.current && - videoReady && - lifecycleZones?.map((zone) => { - return ( -
- - - -
- ); - })} - - {boxStyle && videoReady && ( -
-
-
- )} - - {/* Attribute box overlay */} - {attributeBoxStyle && videoReady && ( -
- )} - - {/* Path overlay */} - {videoRef.current && - videoReady && - pathPoints && - pathPoints.length > 0 && ( -
- - - -
- )} -
- -
- )} - - {showLifecycle && ( - <> -
- {t("trackingDetails.title")} - -
- - - - - - - {t("trackingDetails.adjustAnnotationSettings")} - - - -
-
- -
-
- {t("trackingDetails.scrollViewTips")} -
-
- {t("trackingDetails.count", { - first: selectedIndex + 1, - second: eventSequence?.length ?? 0, - })} -
-
- {config?.cameras[event.camera]?.onvif.autotracking - .enabled_in_config && ( -
- {t("trackingDetails.autoTrackingTips")} -
- )} - {showControls && ( - - )} - -
-
-
-
{ - e.stopPropagation(); - setTimeIndex(event.start_time ?? 0); - }} - role="button" - > -
- {getIconForLabel( - event.sub_label ? event.label + "-verified" : event.label, - "size-4 text-white", - )} -
-
- {label} - - {formattedStart ?? ""} - {formattedEnd ?? ""} - - {event.data?.recognized_license_plate && ( - <> - · -
- - {event.data.recognized_license_plate} - -
- - )} -
-
-
- -
- {!eventSequence ? ( - - ) : eventSequence.length === 0 ? ( -
- {t("detail.noObjectDetailData", { ns: "views/events" })} -
- ) : ( -
-
-
-
- {eventSequence.map((item, idx) => { - const isActive = - Math.abs( - (propTimeIndex ?? 0) - (item.timestamp ?? 0), - ) <= 0.5; - const formattedEventTimestamp = config - ? formatUnixTimestampToDateTime(item.timestamp ?? 0, { - timezone: config.ui.timezone, - date_format: - config.ui.time_format == "24hour" - ? t( - "time.formattedTimestampHourMinuteSecond.24hour", - { ns: "common" }, - ) - : t( - "time.formattedTimestampHourMinuteSecond.12hour", - { ns: "common" }, - ), - time_style: "medium", - date_style: "medium", - }) - : ""; - - const ratio = - Array.isArray(item.data.box) && - item.data.box.length >= 4 - ? ( - aspectRatio * - (item.data.box[2] / item.data.box[3]) - ).toFixed(2) - : "N/A"; - const areaPx = - Array.isArray(item.data.box) && - item.data.box.length >= 4 - ? Math.round( - (config.cameras[event.camera]?.detect?.width ?? - 0) * - (config.cameras[event.camera]?.detect - ?.height ?? 0) * - (item.data.box[2] * item.data.box[3]), - ) - : undefined; - const areaPct = - Array.isArray(item.data.box) && - item.data.box.length >= 4 - ? (item.data.box[2] * item.data.box[3]).toFixed(4) - : undefined; - - return ( - { - setTimeIndex(item.timestamp ?? 0); - handleSetBox( - item.data.box ?? [], - item.data.attribute_box, - ); - setLifecycleZones(item.data.zones); - setSelectedZone(""); - }} - setSelectedZone={setSelectedZone} - getZoneColor={getZoneColor} - /> - ); - })} -
-
- )} -
-
-
- - )} -
- ); -} - -type GetTimelineIconParams = { - lifecycleItem: TrackingDetailsSequence; - className?: string; -}; - -export function LifecycleIcon({ - lifecycleItem, - className, -}: GetTimelineIconParams) { - switch (lifecycleItem.class_type) { - case "visible": - return ; - case "gone": - return ; - case "active": - return ; - case "stationary": - return ; - case "entered_zone": - return ; - case "attribute": - switch (lifecycleItem.data?.attribute) { - case "face": - return ; - case "license_plate": - return ; - default: - return ; - } - case "heard": - return ; - case "external": - return ; - default: - return null; - } -} - -type LifecycleIconRowProps = { - item: TrackingDetailsSequence; - isActive?: boolean; - formattedEventTimestamp: string; - ratio: string; - areaPx?: number; - areaPct?: string; - onClick: () => void; - setSelectedZone: (z: string) => void; - getZoneColor: (zoneName: string) => number[] | undefined; -}; - -function LifecycleIconRow({ - item, - isActive, - formattedEventTimestamp, - ratio, - areaPx, - areaPct, - onClick, - setSelectedZone, - getZoneColor, -}: LifecycleIconRowProps) { - const { t } = useTranslation(["views/explore", "components/player"]); - const { data: config } = useSWR("config"); - const [isOpen, setIsOpen] = useState(false); - - const navigate = useNavigate(); - - return ( -
-
-
- -
- -
-
-
- {getLifecycleItemDescription(item)} -
-
-
- - {t("trackingDetails.lifecycleItemDesc.header.ratio")} - - {ratio} -
-
- - {t("trackingDetails.lifecycleItemDesc.header.area")} - - {areaPx !== undefined && areaPct !== undefined ? ( - - {t("information.pixels", { ns: "common", area: areaPx })} ·{" "} - {areaPct}% - - ) : ( - N/A - )} -
- - {item.data?.zones && item.data.zones.length > 0 && ( -
- {item.data.zones.map((zone, zidx) => { - const color = getZoneColor(zone)?.join(",") ?? "0,0,0"; - return ( - { - e.stopPropagation(); - setSelectedZone(zone); - }} - style={{ - borderColor: `rgba(${color}, 0.6)`, - background: `rgba(${color}, 0.08)`, - }} - > - - - {zone.replaceAll("_", " ")} - - - ); - })} -
- )} -
-
-
-
-
-
{formattedEventTimestamp}
- {(config?.plus?.enabled || item.data.box) && ( - - -
- -
-
- - - {config?.plus?.enabled && ( - { - const resp = await axios.post( - `/${item.camera}/plus/${item.timestamp}`, - ); - - if (resp && resp.status == 200) { - toast.success( - t("toast.success.submittedFrigatePlus", { - ns: "components/player", - }), - { - position: "top-center", - }, - ); - } else { - toast.success( - t("toast.error.submitFrigatePlusFailed", { - ns: "components/player", - }), - { - position: "top-center", - }, - ); - } - }} - > - {t("itemMenu.submitToPlus.label")} - - )} - {item.data.box && ( - { - setIsOpen(false); - setTimeout(() => { - navigate( - `/settings?page=masksAndZones&camera=${item.camera}&object_mask=${item.data.box}`, - ); - }, 0); - }} - > - {t("trackingDetails.createObjectMask")} - - )} - - -
- )} -
-
-
-
- ); -}