mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-09-28 17:53:51 +02:00
* Add option to not trim clip * Improve API * Update snapshot for new best objects * Fix missing strings * Convert to separate key * Always include bounding box on snapshots * improve autotracking relative zooming time calculation * update proxy docs to note the need for comma separated header roles * Add count translation * tracked object lifecycle i18n fix * update speed estimation docs * clarity * Re-initialize onvif information when toggling camera on live view * Move time ago to card info and add face area * Clarify face recognition docs * Increase minimum face recognition area * use clipFrom to in vod module endpoint to start at the correct time * Cleanup media api * Don't change duration * Use search detail dialog for face library * Move to segment based * Cleanup * Add back duration modification * clean up docs --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
import { useCallback } from "react";
|
|
import { LifecycleClassType, Position } from "@/types/timeline";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
|
import { getLifecycleItemDescription } from "@/utils/lifecycleUtil";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
type ObjectPathProps = {
|
|
positions?: Position[];
|
|
color?: number[];
|
|
width?: number;
|
|
pointRadius?: number;
|
|
imgRef: React.RefObject<HTMLImageElement>;
|
|
onPointClick?: (index: number) => void;
|
|
visible?: boolean;
|
|
};
|
|
|
|
const typeColorMap: Partial<
|
|
Record<LifecycleClassType, [number, number, number]>
|
|
> = {
|
|
[LifecycleClassType.VISIBLE]: [0, 255, 0], // Green
|
|
[LifecycleClassType.GONE]: [255, 0, 0], // Red
|
|
[LifecycleClassType.ENTERED_ZONE]: [255, 165, 0], // Orange
|
|
[LifecycleClassType.ATTRIBUTE]: [128, 0, 128], // Purple
|
|
[LifecycleClassType.ACTIVE]: [255, 255, 0], // Yellow
|
|
[LifecycleClassType.STATIONARY]: [128, 128, 128], // Gray
|
|
[LifecycleClassType.HEARD]: [0, 255, 255], // Cyan
|
|
[LifecycleClassType.EXTERNAL]: [165, 42, 42], // Brown
|
|
};
|
|
|
|
export function ObjectPath({
|
|
positions,
|
|
color = [0, 0, 255],
|
|
width = 2,
|
|
pointRadius = 4,
|
|
imgRef,
|
|
onPointClick,
|
|
visible = true,
|
|
}: ObjectPathProps) {
|
|
const { t } = useTranslation(["views/explore"]);
|
|
const getAbsolutePositions = useCallback(() => {
|
|
if (!imgRef.current || !positions) return [];
|
|
const imgRect = imgRef.current.getBoundingClientRect();
|
|
return positions.map((pos) => ({
|
|
x: pos.x * imgRect.width,
|
|
y: pos.y * imgRect.height,
|
|
timestamp: pos.timestamp,
|
|
lifecycle_item: pos.lifecycle_item,
|
|
}));
|
|
}, [positions, imgRef]);
|
|
|
|
const generateStraightPath = useCallback((points: Position[]) => {
|
|
if (!points || points.length < 2) return "";
|
|
let path = `M ${points[0].x} ${points[0].y}`;
|
|
for (let i = 1; i < points.length; i++) {
|
|
path += ` L ${points[i].x} ${points[i].y}`;
|
|
}
|
|
return path;
|
|
}, []);
|
|
|
|
const getPointColor = (baseColor: number[], type?: LifecycleClassType) => {
|
|
if (type) {
|
|
const typeColor = typeColorMap[type];
|
|
if (typeColor) {
|
|
return `rgb(${typeColor.join(",")})`;
|
|
}
|
|
}
|
|
// normal path point
|
|
return `rgb(${baseColor.map((c) => Math.max(0, c - 10)).join(",")})`;
|
|
};
|
|
|
|
if (!imgRef.current || !visible) return null;
|
|
const absolutePositions = getAbsolutePositions();
|
|
const lineColor = `rgb(${color.join(",")})`;
|
|
|
|
return (
|
|
<g>
|
|
<path
|
|
d={generateStraightPath(absolutePositions)}
|
|
fill="none"
|
|
stroke={lineColor}
|
|
strokeWidth={width}
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
{absolutePositions.map((pos, index) => (
|
|
<Tooltip key={`point-${index}`}>
|
|
<TooltipTrigger asChild>
|
|
<circle
|
|
cx={pos.x}
|
|
cy={pos.y}
|
|
r={pointRadius}
|
|
fill={getPointColor(color, pos.lifecycle_item?.class_type)}
|
|
stroke="white"
|
|
strokeWidth={width / 2}
|
|
onClick={() => onPointClick && onPointClick(index)}
|
|
style={{ cursor: "pointer" }}
|
|
/>
|
|
</TooltipTrigger>
|
|
<TooltipPortal>
|
|
<TooltipContent side="top" className="smart-capitalize">
|
|
{pos.lifecycle_item
|
|
? getLifecycleItemDescription(pos.lifecycle_item)
|
|
: t("objectLifecycle.trackedPoint")}
|
|
</TooltipContent>
|
|
</TooltipPortal>
|
|
</Tooltip>
|
|
))}
|
|
</g>
|
|
);
|
|
}
|