highlight line on canvas when entering value in zone edit pane

This commit is contained in:
Josh Hawkins 2024-12-08 12:03:18 -06:00
parent 9be1454f10
commit bb2a1e12cc
4 changed files with 117 additions and 9 deletions

View File

@ -17,6 +17,7 @@ type PolygonCanvasProps = {
activePolygonIndex: number | undefined; activePolygonIndex: number | undefined;
hoveredPolygonIndex: number | null; hoveredPolygonIndex: number | null;
selectedZoneMask: PolygonType[] | undefined; selectedZoneMask: PolygonType[] | undefined;
activeLine?: number;
}; };
export function PolygonCanvas({ export function PolygonCanvas({
@ -29,6 +30,7 @@ export function PolygonCanvas({
activePolygonIndex, activePolygonIndex,
hoveredPolygonIndex, hoveredPolygonIndex,
selectedZoneMask, selectedZoneMask,
activeLine,
}: PolygonCanvasProps) { }: PolygonCanvasProps) {
const [isLoaded, setIsLoaded] = useState(false); const [isLoaded, setIsLoaded] = useState(false);
const [image, setImage] = useState<HTMLImageElement | undefined>(); const [image, setImage] = useState<HTMLImageElement | undefined>();
@ -281,12 +283,14 @@ export function PolygonCanvas({
stageRef={stageRef} stageRef={stageRef}
key={index} key={index}
points={polygon.points} points={polygon.points}
distances={polygon.distances}
isActive={index === activePolygonIndex} isActive={index === activePolygonIndex}
isHovered={index === hoveredPolygonIndex} isHovered={index === hoveredPolygonIndex}
isFinished={polygon.isFinished} isFinished={polygon.isFinished}
color={polygon.color} color={polygon.color}
handlePointDragMove={handlePointDragMove} handlePointDragMove={handlePointDragMove}
handleGroupDragEnd={handleGroupDragEnd} handleGroupDragEnd={handleGroupDragEnd}
activeLine={activeLine}
/> />
), ),
)} )}
@ -298,12 +302,14 @@ export function PolygonCanvas({
stageRef={stageRef} stageRef={stageRef}
key={activePolygonIndex} key={activePolygonIndex}
points={polygons[activePolygonIndex].points} points={polygons[activePolygonIndex].points}
distances={polygons[activePolygonIndex].distances}
isActive={true} isActive={true}
isHovered={activePolygonIndex === hoveredPolygonIndex} isHovered={activePolygonIndex === hoveredPolygonIndex}
isFinished={polygons[activePolygonIndex].isFinished} isFinished={polygons[activePolygonIndex].isFinished}
color={polygons[activePolygonIndex].color} color={polygons[activePolygonIndex].color}
handlePointDragMove={handlePointDragMove} handlePointDragMove={handlePointDragMove}
handleGroupDragEnd={handleGroupDragEnd} handleGroupDragEnd={handleGroupDragEnd}
activeLine={activeLine}
/> />
)} )}
</Layer> </Layer>

View File

@ -6,7 +6,7 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { Line, Circle, Group } from "react-konva"; import { Line, Circle, Group, Text, Rect } from "react-konva";
import { import {
minMax, minMax,
toRGBColorString, toRGBColorString,
@ -20,23 +20,27 @@ import { Vector2d } from "konva/lib/types";
type PolygonDrawerProps = { type PolygonDrawerProps = {
stageRef: RefObject<Konva.Stage>; stageRef: RefObject<Konva.Stage>;
points: number[][]; points: number[][];
distances: number[];
isActive: boolean; isActive: boolean;
isHovered: boolean; isHovered: boolean;
isFinished: boolean; isFinished: boolean;
color: number[]; color: number[];
handlePointDragMove: (e: KonvaEventObject<MouseEvent | TouchEvent>) => void; handlePointDragMove: (e: KonvaEventObject<MouseEvent | TouchEvent>) => void;
handleGroupDragEnd: (e: KonvaEventObject<MouseEvent | TouchEvent>) => void; handleGroupDragEnd: (e: KonvaEventObject<MouseEvent | TouchEvent>) => void;
activeLine?: number;
}; };
export default function PolygonDrawer({ export default function PolygonDrawer({
stageRef, stageRef,
points, points,
distances,
isActive, isActive,
isHovered, isHovered,
isFinished, isFinished,
color, color,
handlePointDragMove, handlePointDragMove,
handleGroupDragEnd, handleGroupDragEnd,
activeLine,
}: PolygonDrawerProps) { }: PolygonDrawerProps) {
const vertexRadius = 6; const vertexRadius = 6;
const flattenedPoints = useMemo(() => flattenPoints(points), [points]); const flattenedPoints = useMemo(() => flattenPoints(points), [points]);
@ -113,6 +117,33 @@ export default function PolygonDrawer({
stageRef.current.container().style.cursor = cursor; stageRef.current.container().style.cursor = cursor;
}, [stageRef, cursor]); }, [stageRef, cursor]);
// Calculate midpoints for distance labels based on sorted points
const midpoints = useMemo(() => {
const midpointsArray = [];
for (let i = 0; i < points.length; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % points.length];
const midpointX = (p1[0] + p2[0]) / 2;
const midpointY = (p1[1] + p2[1]) / 2;
midpointsArray.push([midpointX, midpointY]);
}
return midpointsArray;
}, [points]);
// Determine the points for the active line
const activeLinePoints = useMemo(() => {
if (
activeLine === undefined ||
activeLine < 1 ||
activeLine > points.length
) {
return [];
}
const p1 = points[activeLine - 1];
const p2 = points[activeLine % points.length];
return [p1[0], p1[1], p2[0], p2[1]];
}, [activeLine, points]);
return ( return (
<Group <Group
name="polygon" name="polygon"
@ -158,6 +189,14 @@ export default function PolygonDrawer({
} }
/> />
)} )}
{isActive && activeLinePoints.length > 0 && (
<Line
points={activeLinePoints}
stroke="white"
strokeWidth={6}
hitStrokeWidth={12}
/>
)}
{points.map((point, index) => { {points.map((point, index) => {
if (!isActive) { if (!isActive) {
return; return;
@ -195,6 +234,43 @@ export default function PolygonDrawer({
/> />
); );
})} })}
{isFinished && (
<Group>
{midpoints.map((midpoint, index) => {
const [x, y] = midpoint;
const distance = distances[index];
if (distance === undefined) return null;
const squareSize = 22;
return (
<Group
key={`distance-group-${index}`}
x={x - squareSize / 2}
y={y - squareSize / 2}
>
<Rect
width={squareSize}
height={squareSize}
fill={colorString(true)}
stroke="white"
strokeWidth={1}
/>
<Text
text={`${distance}`}
width={squareSize}
y={4}
fontSize={16}
fontFamily="Arial"
fill="white"
align="center"
verticalAlign="middle"
/>
</Group>
);
})}
</Group>
)}
</Group> </Group>
); );
} }

View File

@ -40,6 +40,7 @@ type ZoneEditPaneProps = {
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>; setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
onSave?: () => void; onSave?: () => void;
onCancel?: () => void; onCancel?: () => void;
setActiveLine: React.Dispatch<React.SetStateAction<number | undefined>>;
}; };
export default function ZoneEditPane({ export default function ZoneEditPane({
@ -52,6 +53,7 @@ export default function ZoneEditPane({
setIsLoading, setIsLoading,
onSave, onSave,
onCancel, onCancel,
setActiveLine,
}: ZoneEditPaneProps) { }: ZoneEditPaneProps) {
const { data: config, mutate: updateConfig } = const { data: config, mutate: updateConfig } =
useSWR<FrigateConfig>("config"); useSWR<FrigateConfig>("config");
@ -569,9 +571,13 @@ export default function ZoneEditPane({
name="topWidth" name="topWidth"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Top Width</FormLabel> <FormLabel>Line A distance</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="Enter top width..." /> <Input
{...field}
onFocus={() => setActiveLine(1)}
onBlur={() => setActiveLine(undefined)}
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}
@ -581,9 +587,13 @@ export default function ZoneEditPane({
name="bottomWidth" name="bottomWidth"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Bottom Width</FormLabel> <FormLabel>Line B distance</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="Enter bottom width..." /> <Input
{...field}
onFocus={() => setActiveLine(2)}
onBlur={() => setActiveLine(undefined)}
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}
@ -593,9 +603,13 @@ export default function ZoneEditPane({
name="leftDepth" name="leftDepth"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Left Depth</FormLabel> <FormLabel>Line C distance</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="Enter left depth..." /> <Input
{...field}
onFocus={() => setActiveLine(3)}
onBlur={() => setActiveLine(undefined)}
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}
@ -605,9 +619,13 @@ export default function ZoneEditPane({
name="rightDepth" name="rightDepth"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Right Depth</FormLabel> <FormLabel>Line D distance</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="Enter right depth..." /> <Input
{...field}
onFocus={() => setActiveLine(4)}
onBlur={() => setActiveLine(undefined)}
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}

View File

@ -61,6 +61,7 @@ export default function MasksAndZonesView({
); );
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const [editPane, setEditPane] = useState<PolygonType | undefined>(undefined); const [editPane, setEditPane] = useState<PolygonType | undefined>(undefined);
const [activeLine, setActiveLine] = useState<number | undefined>();
const { addMessage } = useContext(StatusBarMessagesContext)!; const { addMessage } = useContext(StatusBarMessagesContext)!;
@ -161,6 +162,7 @@ export default function MasksAndZonesView({
...(allPolygons || []), ...(allPolygons || []),
{ {
points: [], points: [],
distances: [],
isFinished: false, isFinished: false,
type, type,
typeIndex: 9999, typeIndex: 9999,
@ -238,6 +240,7 @@ export default function MasksAndZonesView({
scaledWidth, scaledWidth,
scaledHeight, scaledHeight,
), ),
distances: zoneData.distances.map((distance) => parseFloat(distance)),
isFinished: true, isFinished: true,
color: zoneData.color, color: zoneData.color,
}), }),
@ -267,6 +270,7 @@ export default function MasksAndZonesView({
scaledWidth, scaledWidth,
scaledHeight, scaledHeight,
), ),
distances: [],
isFinished: true, isFinished: true,
color: [0, 0, 255], color: [0, 0, 255],
})); }));
@ -290,6 +294,7 @@ export default function MasksAndZonesView({
scaledWidth, scaledWidth,
scaledHeight, scaledHeight,
), ),
distances: [],
isFinished: true, isFinished: true,
color: [128, 128, 128], color: [128, 128, 128],
})); }));
@ -316,6 +321,7 @@ export default function MasksAndZonesView({
scaledWidth, scaledWidth,
scaledHeight, scaledHeight,
), ),
distances: [],
isFinished: true, isFinished: true,
color: [128, 128, 128], color: [128, 128, 128],
}; };
@ -391,6 +397,7 @@ export default function MasksAndZonesView({
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
onCancel={handleCancel} onCancel={handleCancel}
onSave={handleSave} onSave={handleSave}
setActiveLine={setActiveLine}
/> />
)} )}
{editPane == "motion_mask" && ( {editPane == "motion_mask" && (
@ -653,6 +660,7 @@ export default function MasksAndZonesView({
activePolygonIndex={activePolygonIndex} activePolygonIndex={activePolygonIndex}
hoveredPolygonIndex={hoveredPolygonIndex} hoveredPolygonIndex={hoveredPolygonIndex}
selectedZoneMask={selectedZoneMask} selectedZoneMask={selectedZoneMask}
activeLine={activeLine}
/> />
) : ( ) : (
<Skeleton className="size-full" /> <Skeleton className="size-full" />