mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-14 19:12:08 +03:00
memoize to prevent unnecessary renders
This commit is contained in:
parent
ddd029b7d9
commit
5fdd426e12
@ -103,6 +103,7 @@ export default function ObjectTrackOverlay({
|
|||||||
}));
|
}));
|
||||||
}, [config, camera, getZoneColor, currentObjectZones]);
|
}, [config, camera, getZoneColor, currentObjectZones]);
|
||||||
|
|
||||||
|
// get saved path points from event
|
||||||
const savedPathPoints = useMemo(() => {
|
const savedPathPoints = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
eventData?.[0].data?.path_data?.map(
|
eventData?.[0].data?.path_data?.map(
|
||||||
@ -116,6 +117,7 @@ export default function ObjectTrackOverlay({
|
|||||||
);
|
);
|
||||||
}, [eventData]);
|
}, [eventData]);
|
||||||
|
|
||||||
|
// timeline points for selected event
|
||||||
const eventSequencePoints = useMemo(() => {
|
const eventSequencePoints = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
objectTimeline
|
objectTimeline
|
||||||
@ -124,8 +126,8 @@ export default function ObjectTrackOverlay({
|
|||||||
const [left, top, width, height] = event.data.box!;
|
const [left, top, width, height] = event.data.box!;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: left + width / 2, // Center x-coordinate
|
x: left + width / 2, // Center x
|
||||||
y: top + height, // Bottom y-coordinate
|
y: top + height, // Bottom y
|
||||||
timestamp: event.timestamp,
|
timestamp: event.timestamp,
|
||||||
lifecycle_item: event,
|
lifecycle_item: event,
|
||||||
};
|
};
|
||||||
@ -152,6 +154,7 @@ export default function ObjectTrackOverlay({
|
|||||||
);
|
);
|
||||||
}, [savedPathPoints, eventSequencePoints, config, camera, currentTime]);
|
}, [savedPathPoints, eventSequencePoints, config, camera, currentTime]);
|
||||||
|
|
||||||
|
// get absolute positions on the svg canvas for each point
|
||||||
const getAbsolutePositions = useCallback(() => {
|
const getAbsolutePositions = useCallback(() => {
|
||||||
if (!pathPoints) return [];
|
if (!pathPoints) return [];
|
||||||
return pathPoints.map((point) => {
|
return pathPoints.map((point) => {
|
||||||
@ -165,7 +168,7 @@ export default function ObjectTrackOverlay({
|
|||||||
timestamp: point.timestamp,
|
timestamp: point.timestamp,
|
||||||
lifecycle_item:
|
lifecycle_item:
|
||||||
timelineEntry ||
|
timelineEntry ||
|
||||||
(point.box
|
(point.box // normal path point
|
||||||
? {
|
? {
|
||||||
timestamp: point.timestamp,
|
timestamp: point.timestamp,
|
||||||
camera: camera,
|
camera: camera,
|
||||||
@ -220,22 +223,88 @@ export default function ObjectTrackOverlay({
|
|||||||
[typeColorMap],
|
[typeColorMap],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handlePointClick = useCallback(
|
||||||
|
(timestamp: number) => {
|
||||||
|
onSeekToTime?.(timestamp);
|
||||||
|
},
|
||||||
|
[onSeekToTime],
|
||||||
|
);
|
||||||
|
|
||||||
|
// render bounding box for object at current time if we have a timeline entry
|
||||||
|
const currentBoundingBox = useMemo(() => {
|
||||||
|
if (!objectTimeline) return null;
|
||||||
|
|
||||||
|
// Find the most recent timeline event at or before effective current time with a bounding box
|
||||||
|
const relevantEvents = objectTimeline
|
||||||
|
.filter(
|
||||||
|
(event) => event.timestamp <= effectiveCurrentTime && event.data.box,
|
||||||
|
)
|
||||||
|
.sort((a, b) => b.timestamp - a.timestamp); // Most recent first
|
||||||
|
|
||||||
|
const currentEvent = relevantEvents[0];
|
||||||
|
|
||||||
|
if (!currentEvent?.data.box) return null;
|
||||||
|
|
||||||
|
const [left, top, width, height] = currentEvent.data.box;
|
||||||
|
return {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
centerX: left + width / 2,
|
||||||
|
centerY: top + height,
|
||||||
|
};
|
||||||
|
}, [objectTimeline, effectiveCurrentTime]);
|
||||||
|
|
||||||
|
const objectColor = useMemo(() => {
|
||||||
|
return pathPoints[0]?.label
|
||||||
|
? getObjectColor(pathPoints[0].label)
|
||||||
|
: "rgb(255, 0, 0)";
|
||||||
|
}, [pathPoints, getObjectColor]);
|
||||||
|
|
||||||
|
const objectColorArray = useMemo(() => {
|
||||||
|
return pathPoints[0]?.label
|
||||||
|
? getObjectColor(pathPoints[0].label).match(/\d+/g)?.map(Number) || [
|
||||||
|
255, 0, 0,
|
||||||
|
]
|
||||||
|
: [255, 0, 0];
|
||||||
|
}, [pathPoints, getObjectColor]);
|
||||||
|
|
||||||
|
const absolutePositions = useMemo(
|
||||||
|
() => getAbsolutePositions(),
|
||||||
|
[getAbsolutePositions],
|
||||||
|
);
|
||||||
|
|
||||||
|
// render any zones for object at current time
|
||||||
|
const zonePolygons = useMemo(() => {
|
||||||
|
return zones.map((zone) => {
|
||||||
|
// Convert zone coordinates from normalized (0-1) to pixel coordinates
|
||||||
|
const points = zone.coordinates
|
||||||
|
.split(",")
|
||||||
|
.map(Number.parseFloat)
|
||||||
|
.reduce((acc: string[], value, index) => {
|
||||||
|
const isXCoordinate = index % 2 === 0;
|
||||||
|
const coordinate = isXCoordinate
|
||||||
|
? value * videoWidth
|
||||||
|
: value * videoHeight;
|
||||||
|
acc.push(coordinate.toString());
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.join(",");
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: zone.name,
|
||||||
|
points,
|
||||||
|
fill: `rgba(${zone.color.replace("rgb(", "").replace(")", "")}, 0.3)`,
|
||||||
|
stroke: zone.color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [zones, videoWidth, videoHeight]);
|
||||||
|
|
||||||
if (!pathPoints.length || !config) {
|
if (!pathPoints.length || !config) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the object color from the first point's label
|
|
||||||
const objectColor = pathPoints[0]?.label
|
|
||||||
? getObjectColor(pathPoints[0].label)
|
|
||||||
: "rgb(255, 0, 0)";
|
|
||||||
const objectColorArray = pathPoints[0]?.label
|
|
||||||
? getObjectColor(pathPoints[0].label).match(/\d+/g)?.map(Number) || [
|
|
||||||
255, 0, 0,
|
|
||||||
]
|
|
||||||
: [255, 0, 0];
|
|
||||||
|
|
||||||
const absolutePositions = getAbsolutePositions();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className={cn(className)}
|
className={cn(className)}
|
||||||
@ -246,35 +315,17 @@ export default function ObjectTrackOverlay({
|
|||||||
}}
|
}}
|
||||||
preserveAspectRatio="xMidYMid slice"
|
preserveAspectRatio="xMidYMid slice"
|
||||||
>
|
>
|
||||||
{/* Render zones */}
|
{zonePolygons.map((zone) => (
|
||||||
{zones.map((zone) => {
|
<polygon
|
||||||
// Convert zone coordinates from normalized (0-1) to pixel coordinates
|
key={zone.key}
|
||||||
const points = zone.coordinates
|
points={zone.points}
|
||||||
.split(",")
|
fill={zone.fill}
|
||||||
.map(Number.parseFloat)
|
stroke={zone.stroke}
|
||||||
.reduce((acc: string[], value, index) => {
|
strokeWidth="5"
|
||||||
const isXCoordinate = index % 2 === 0;
|
opacity="0.7"
|
||||||
const coordinate = isXCoordinate
|
/>
|
||||||
? value * videoWidth
|
))}
|
||||||
: value * videoHeight;
|
|
||||||
acc.push(coordinate.toString());
|
|
||||||
return acc;
|
|
||||||
}, [])
|
|
||||||
.join(",");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<polygon
|
|
||||||
key={zone.name}
|
|
||||||
points={points}
|
|
||||||
fill={`rgba(${zone.color.replace("rgb(", "").replace(")", "")}, 0.3)`}
|
|
||||||
stroke={zone.color}
|
|
||||||
strokeWidth="5"
|
|
||||||
opacity="0.7"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* Draw path connecting the points */}
|
|
||||||
{absolutePositions.length > 1 && (
|
{absolutePositions.length > 1 && (
|
||||||
<path
|
<path
|
||||||
d={generateStraightPath(absolutePositions)}
|
d={generateStraightPath(absolutePositions)}
|
||||||
@ -286,7 +337,6 @@ export default function ObjectTrackOverlay({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Draw points with tooltips */}
|
|
||||||
{absolutePositions.map((pos, index) => (
|
{absolutePositions.map((pos, index) => (
|
||||||
<Tooltip key={`point-${index}`}>
|
<Tooltip key={`point-${index}`}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@ -301,9 +351,7 @@ export default function ObjectTrackOverlay({
|
|||||||
stroke="white"
|
stroke="white"
|
||||||
strokeWidth="3"
|
strokeWidth="3"
|
||||||
style={{ cursor: onSeekToTime ? "pointer" : "default" }}
|
style={{ cursor: onSeekToTime ? "pointer" : "default" }}
|
||||||
onClick={() => {
|
onClick={() => handlePointClick(pos.timestamp)}
|
||||||
onSeekToTime?.(pos.timestamp);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
@ -321,53 +369,30 @@ export default function ObjectTrackOverlay({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Highlight current position with bounding box */}
|
{currentBoundingBox && (
|
||||||
{(() => {
|
<g>
|
||||||
if (!objectTimeline) return null;
|
<rect
|
||||||
|
x={currentBoundingBox.left * videoWidth}
|
||||||
|
y={currentBoundingBox.top * videoHeight}
|
||||||
|
width={currentBoundingBox.width * videoWidth}
|
||||||
|
height={currentBoundingBox.height * videoHeight}
|
||||||
|
fill="none"
|
||||||
|
stroke={objectColor}
|
||||||
|
strokeWidth="5"
|
||||||
|
opacity="0.9"
|
||||||
|
/>
|
||||||
|
|
||||||
// Find the most recent timeline event at or before effective current time with a bounding box
|
<circle
|
||||||
const relevantEvents = objectTimeline
|
cx={currentBoundingBox.centerX * videoWidth}
|
||||||
.filter(
|
cy={currentBoundingBox.centerY * videoHeight}
|
||||||
(event) =>
|
r="5"
|
||||||
event.timestamp <= effectiveCurrentTime && event.data.box,
|
fill="rgb(255, 255, 0)" // yellow highlight
|
||||||
)
|
stroke={objectColor}
|
||||||
.sort((a, b) => b.timestamp - a.timestamp); // Most recent first
|
strokeWidth="5"
|
||||||
|
opacity="1"
|
||||||
const currentEvent = relevantEvents[0];
|
/>
|
||||||
|
</g>
|
||||||
if (currentEvent && currentEvent.data.box) {
|
)}
|
||||||
const [left, top, width, height] = currentEvent.data.box;
|
|
||||||
const centerX = left + width / 2;
|
|
||||||
const centerY = top + height;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<g>
|
|
||||||
{/* Bounding box */}
|
|
||||||
<rect
|
|
||||||
x={left * videoWidth}
|
|
||||||
y={top * videoHeight}
|
|
||||||
width={width * videoWidth}
|
|
||||||
height={height * videoHeight}
|
|
||||||
fill="none"
|
|
||||||
stroke={objectColor}
|
|
||||||
strokeWidth="5"
|
|
||||||
opacity="0.9"
|
|
||||||
/>
|
|
||||||
{/* Center point highlight */}
|
|
||||||
<circle
|
|
||||||
cx={centerX * videoWidth}
|
|
||||||
cy={centerY * videoHeight}
|
|
||||||
r="5"
|
|
||||||
fill="rgb(255, 255, 0)" // yellow highlight
|
|
||||||
stroke={objectColor}
|
|
||||||
strokeWidth="5"
|
|
||||||
opacity="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})()}
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user