- Enhanced camera feed state management with region handling and mode changes

This commit is contained in:
2025-11-27 16:16:15 +00:00
parent f7dbde4511
commit bf31f94b32
7 changed files with 190 additions and 135 deletions

View File

@@ -7,6 +7,25 @@ export const initialState: CameraFeedState = {
B: new Map<string, PaintedCell>(), B: new Map<string, PaintedCell>(),
C: new Map<string, PaintedCell>(), C: new Map<string, PaintedCell>(),
}, },
regionsByCamera: {
A: [
{ name: "Region 1", brushColour: "#ff0000" },
{ name: "Region 2", brushColour: "#00ff00" },
{ name: "Region 3", brushColour: "#0400ff" },
],
B: [
{ name: "Region 1", brushColour: "#ff0000" },
{ name: "Region 2", brushColour: "#00ff00" },
],
C: [{ name: "Region 1", brushColour: "#ff0000" }],
},
selectedRegionIndex: 0,
modeByCamera: {
A: "brush",
B: "brush",
C: "brush",
},
}; };
export function reducer(state: CameraFeedState, action: CameraFeedAction) { export function reducer(state: CameraFeedState, action: CameraFeedAction) {
@@ -16,6 +35,60 @@ export function reducer(state: CameraFeedState, action: CameraFeedAction) {
...state, ...state,
cameraFeedID: action.payload, cameraFeedID: action.payload,
}; };
case "CHANGE_MODE":
return {
...state,
modeByCamera: {
...state.modeByCamera,
[action.payload.cameraFeedID]: action.payload.mode,
},
};
case "SET_SELECTED_REGION_INDEX":
return {
...state,
selectedRegionIndex: action.payload,
};
case "SET_SELECTED_REGION_COLOUR":
return {
...state,
regionsByCamera: {
...state.regionsByCamera,
[action.payload.cameraFeedID]: state.regionsByCamera[action.payload.cameraFeedID].map((region) =>
region.name === action.payload.regionName ? { ...region, brushColour: action.payload.newColour } : region,
),
},
};
case "ADD_NEW_REGION":
return {
...state,
regionsByCamera: {
...state.regionsByCamera,
[action.payload.cameraFeedID]: [
...state.regionsByCamera[action.payload.cameraFeedID],
{ name: action.payload.regionName, brushColour: action.payload.brushColour },
],
},
};
case "REMOVE_REGION":
console.log(action.payload);
return {
...state,
regionsByCamera: {
...state.regionsByCamera,
[action.payload.cameraFeedID]: state.regionsByCamera[action.payload.cameraFeedID].filter(
(region) => region.name !== action.payload.regionName,
),
},
};
case "RESET_PAINTED_CELLS":
return {
...state,
paintedCells: {
...state.paintedCells,
[state.cameraFeedID]: new Map<string, PaintedCell>(),
},
};
default: default:
return state; return state;
} }

View File

@@ -1,52 +1,17 @@
import { useRef, useState } from "react"; import { useState } from "react";
import VideoFeedGridPainter from "./Video/VideoFeedGridPainter"; import VideoFeedGridPainter from "./Video/VideoFeedGridPainter";
import CameraSettings from "./CameraSettings/CameraSettings"; import CameraSettings from "./CameraSettings/CameraSettings";
import type { PaintedCell, Region } from "../../../types/types";
import PlatePatch from "./PlatePatch/PlatePatch"; import PlatePatch from "./PlatePatch/PlatePatch";
const CameraGrid = () => { const CameraGrid = () => {
const [regions, setRegions] = useState<Region[]>([
{ name: "Region 1", brushColour: "#ff0000" },
{ name: "Region 2", brushColour: "#00ff00" },
{ name: "Region 3", brushColour: "#0400ff" },
]);
const [selectedRegionIndex, setSelectedRegionIndex] = useState(0);
const [mode, setMode] = useState("");
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const updateRegionColour = (index: number, newColour: string) => {
setRegions((prev) => prev.map((r, i) => (i === index ? { ...r, brushColour: newColour } : r)));
};
const paintedCellsRef = useRef<Map<string, PaintedCell>>(new Map());
return ( return (
<div className="grid grid-cols-1 md:grid-cols-5 md:grid-rows-5 max-h-screen"> <div className="grid grid-cols-1 md:grid-cols-5 md:grid-rows-5 max-h-screen">
<VideoFeedGridPainter <VideoFeedGridPainter />
regions={regions} <CameraSettings tabIndex={tabIndex} setTabIndex={setTabIndex} />
selectedRegionIndex={selectedRegionIndex}
mode={mode}
paintedCells={paintedCellsRef}
/>
<CameraSettings
regions={regions}
selectedRegionIndex={selectedRegionIndex}
onSelectRegion={setSelectedRegionIndex}
onChangeRegionColour={updateRegionColour}
mode={mode}
onSelectMode={setMode}
tabIndex={tabIndex}
setTabIndex={setTabIndex}
paintedCells={paintedCellsRef}
onAddRegion={() => {
setRegions((prev) => [...prev, { name: `Region ${prev.length + 1}`, brushColour: "#ffffff" }]);
}}
OnRemoveRegion={() => {
setRegions((prev) => prev.filter((_, i) => i !== selectedRegionIndex));
setSelectedRegionIndex((prev) => (prev > 0 ? prev - 1 : 0));
}}
/>
<PlatePatch /> <PlatePatch />
</div> </div>
); );

View File

@@ -1,33 +1,45 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext";
import RegionSelector from "./RegionSelector";
type CameraPanelProps = { type CameraPanelProps = {
tabIndex: number; tabIndex: number;
}; };
const CameraPanel = ({ tabIndex }: CameraPanelProps) => { const CameraPanel = ({ tabIndex }: CameraPanelProps) => {
const { dispatch } = useCameraFeedContext(); const { state, dispatch } = useCameraFeedContext();
const cameraFeedID = state.cameraFeedID;
const regions = state.regionsByCamera[cameraFeedID];
const selectedRegionIndex = state.selectedRegionIndex;
const mode = state.modeByCamera[cameraFeedID];
useEffect(() => { useEffect(() => {
const mapIndextoCameraId = () => { const mapIndextoCameraId = () => {
switch (tabIndex) { switch (tabIndex) {
case 1: case 0:
return "A"; return "A";
case 2: case 1:
return "B"; return "B";
case 3: case 2:
return "C"; return "C";
default: default:
return null; return "A";
} }
}; };
const cameraId = mapIndextoCameraId(); const cameraId = mapIndextoCameraId();
console.log(cameraId);
dispatch({ type: "SET_CAMERA_FEED", payload: cameraId }); dispatch({ type: "SET_CAMERA_FEED", payload: cameraId });
}, [dispatch, tabIndex]); }, [dispatch, tabIndex]);
return <div>CameraPanel</div>; return (
<RegionSelector
regions={regions}
selectedRegionIndex={selectedRegionIndex}
mode={mode}
cameraFeedID={cameraFeedID}
/>
);
}; };
export default CameraPanel; export default CameraPanel;

View File

@@ -1,38 +1,14 @@
import Card from "../../../../ui/Card"; import Card from "../../../../ui/Card";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import "react-tabs/style/react-tabs.css"; import "react-tabs/style/react-tabs.css";
import RegionSelector from "./RegionSelector";
import type { PaintedCell, Region } from "../../../../types/types";
import type { RefObject } from "react";
import CameraPanel from "./CameraPanel"; import CameraPanel from "./CameraPanel";
type CameraSettingsProps = { type CameraSettingsProps = {
regions: Region[];
selectedRegionIndex: number;
onSelectRegion: (index: number) => void;
onChangeRegionColour: (index: number, colour: string) => void;
mode: string;
onSelectMode: (mode: string) => void;
setTabIndex: (tabIndex: number) => void; setTabIndex: (tabIndex: number) => void;
tabIndex: number; tabIndex: number;
paintedCells: RefObject<Map<string, PaintedCell>>;
onAddRegion: () => void;
OnRemoveRegion: () => void;
}; };
const CameraSettings = ({ const CameraSettings = ({ tabIndex, setTabIndex }: CameraSettingsProps) => {
regions,
selectedRegionIndex,
onSelectRegion,
onChangeRegionColour,
mode,
onSelectMode,
tabIndex,
setTabIndex,
paintedCells,
onAddRegion,
OnRemoveRegion,
}: CameraSettingsProps) => {
return ( return (
<Card className="p-4 col-span-3 row-span-5 col-start-3 md:col-span-3 md:row-span-5 max-h-screen overflow-auto"> <Card className="p-4 col-span-3 row-span-5 col-start-3 md:col-span-3 md:row-span-5 max-h-screen overflow-auto">
<Tabs <Tabs
@@ -41,24 +17,10 @@ const CameraSettings = ({
onSelect={(index) => setTabIndex(index)} onSelect={(index) => setTabIndex(index)}
> >
<TabList> <TabList>
<Tab>Target Detection</Tab> <Tab>Camera A</Tab>
<Tab>Camera 1</Tab> <Tab>Camera B</Tab>
<Tab>Camera 2</Tab> <Tab>Camera C</Tab>
<Tab>Camera 3</Tab>
</TabList> </TabList>
<TabPanel>
<RegionSelector
regions={regions}
selectedRegionIndex={selectedRegionIndex}
onSelectRegion={onSelectRegion}
onChangeRegionColour={onChangeRegionColour}
mode={mode}
onSelectMode={onSelectMode}
paintedCells={paintedCells}
onAddRegion={onAddRegion}
OnRemoveRegion={OnRemoveRegion}
/>
</TabPanel>
<TabPanel> <TabPanel>
<CameraPanel tabIndex={tabIndex} /> <CameraPanel tabIndex={tabIndex} />
</TabPanel> </TabPanel>

View File

@@ -1,45 +1,57 @@
import ColourPicker from "./ColourPicker";
import type { PaintedCell, Region } from "../../../../types/types"; import type { PaintedCell, Region } from "../../../../types/types";
import type { RefObject } from "react"; import ColourPicker from "./ColourPicker";
import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext";
type RegionSelectorProps = { type RegionSelectorProps = {
regions: Region[]; regions: Region[];
selectedRegionIndex: number; selectedRegionIndex: number;
onSelectRegion: (index: number) => void;
onChangeRegionColour: (index: number, colour: string) => void;
mode: string; mode: string;
onSelectMode: (mode: string) => void; cameraFeedID: "A" | "B" | "C";
paintedCells: RefObject<Map<string, PaintedCell>>;
onAddRegion: () => void;
OnRemoveRegion: () => void;
}; };
const RegionSelector = ({ const RegionSelector = ({ regions, selectedRegionIndex, mode, cameraFeedID }: RegionSelectorProps) => {
regions, const { dispatch } = useCameraFeedContext();
selectedRegionIndex,
onSelectRegion,
onChangeRegionColour,
mode,
onSelectMode,
paintedCells,
onAddRegion,
OnRemoveRegion,
}: RegionSelectorProps) => {
const handleChange = (e: { target: { value: string } }) => { const handleChange = (e: { target: { value: string } }) => {
onSelectMode(e.target.value); dispatch({ type: "CHANGE_MODE", payload: { cameraFeedID: cameraFeedID, mode: e.target.value } });
}; };
const handleAddClick = () => { const handleAddRegionClick = () => {
onAddRegion(); const regionName = `Region ${regions.length + 1}`;
dispatch({
type: "ADD_NEW_REGION",
payload: { cameraFeedID: cameraFeedID, regionName: regionName, brushColour: "#ffffff" },
});
}; };
const handleResetClick = () => { const handleResetRegion = () => {
const map = paintedCells.current; dispatch({
map.clear(); type: "RESET_PAINTED_CELLS",
payload: { cameraFeedID: cameraFeedID, paintedCells: new Map<string, PaintedCell>() },
});
}; };
const handleRemoveClick = () => { const handleRemoveClick = () => {
OnRemoveRegion(); dispatch({
type: "REMOVE_REGION",
payload: { cameraFeedID: cameraFeedID, regionName: regions[selectedRegionIndex].name },
});
};
const handleModeChange = (newMode: string) => {
dispatch({ type: "CHANGE_MODE", payload: { cameraFeedID: cameraFeedID, mode: newMode } });
};
const handleRegionSelect = (index: number) => {
dispatch({ type: "SET_SELECTED_REGION_INDEX", payload: index });
};
const handleRegionColourChange = (index: number, newColour: string) => {
const regionName = regions[index].name;
dispatch({
type: "SET_SELECTED_REGION_COLOUR",
payload: { cameraFeedID: cameraFeedID, regionName: regionName, newColour: newColour },
});
}; };
return ( return (
@@ -84,7 +96,7 @@ const RegionSelector = ({
<div className="p-2 border border-gray-600 rounded-lg flex flex-col"> <div className="p-2 border border-gray-600 rounded-lg flex flex-col">
<h2 className="text-2xl mb-2">Region Select</h2> <h2 className="text-2xl mb-2">Region Select</h2>
<> <>
{regions.map((region, idx) => { {regions?.map((region, idx) => {
const isSelected = selectedRegionIndex === idx; const isSelected = selectedRegionIndex === idx;
const inputId = `region-${idx}`; const inputId = `region-${idx}`;
return ( return (
@@ -102,20 +114,20 @@ const RegionSelector = ({
name="region" name="region"
className="sr-only" className="sr-only"
onChange={() => { onChange={() => {
onSelectMode("painter"); handleModeChange("painter");
onSelectRegion(idx); handleRegionSelect(idx);
}} }}
/> />
<span className="text-xl">{region.name}</span> <span className="text-xl">{region.name}</span>
</div> </div>
<ColourPicker colour={region.brushColour} setColour={(c: string) => onChangeRegionColour(idx, c)} /> <ColourPicker colour={region.brushColour} setColour={(c: string) => handleRegionColourChange(idx, c)} />
<p className="text-slate-400">{region.brushColour}</p> <p className="text-slate-400">{region.brushColour}</p>
</label> </label>
); );
})} })}
</> </>
<div className=" mx-auto flex flex-row gap-4 mt-4"> <div className=" mx-auto flex flex-row gap-4 mt-4">
<button className="border border-blue-900 bg-blue-700 px-4 rounded-md" onClick={handleAddClick}> <button className="border border-blue-900 bg-blue-700 px-4 rounded-md" onClick={handleAddRegionClick}>
Add Region Add Region
</button> </button>
<button className="border border-red-900 px-4 rounded-md" onClick={handleRemoveClick}> <button className="border border-red-900 px-4 rounded-md" onClick={handleRemoveClick}>
@@ -128,10 +140,10 @@ const RegionSelector = ({
<div className="flex flex-col"> <div className="flex flex-col">
<h2 className="text-2xl mb-2">Actions</h2> <h2 className="text-2xl mb-2">Actions</h2>
<button <button
onClick={handleResetClick} onClick={handleResetRegion}
className="mt-2 px-4 py-2 border border-red-600 rounded-md text-white bg-red-600 w-full md:w-[40%] hover:bg-red-700 hover:cursor-pointer" className="mt-2 px-4 py-2 border border-red-600 rounded-md text-white bg-red-600 w-full md:w-[40%] hover:bg-red-700 hover:cursor-pointer"
> >
Reset Regions Reset Region
</button> </button>
</div> </div>
</div> </div>

View File

@@ -2,7 +2,7 @@ import { useEffect, useRef, useState, type RefObject } from "react";
import { Stage, Layer, Image, Shape } from "react-konva"; import { Stage, Layer, Image, Shape } from "react-konva";
import type { KonvaEventObject } from "konva/lib/Node"; import type { KonvaEventObject } from "konva/lib/Node";
import { useCreateVideoSnapshot } from "../../hooks/useGetvideoSnapshots"; import { useCreateVideoSnapshot } from "../../hooks/useGetvideoSnapshots";
import type { PaintedCell, Region } from "../../../../types/types";
import Card from "../../../../ui/Card"; import Card from "../../../../ui/Card";
import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext";
@@ -11,17 +11,13 @@ const cols = 40;
const size = 20; const size = 20;
const gap = 0; const gap = 0;
type VideoFeedGridPainterProps = { const VideoFeedGridPainter = () => {
regions: Region[]; const { state } = useCameraFeedContext();
selectedRegionIndex: number;
mode: string;
paintedCells: RefObject<Map<string, PaintedCell>>;
};
const VideoFeedGridPainter = ({ regions, selectedRegionIndex, mode }: VideoFeedGridPainterProps) => {
const { state, dispatch } = useCameraFeedContext();
const cameraFeedID = state.cameraFeedID; const cameraFeedID = state.cameraFeedID;
const paintedCells = state.paintedCells[cameraFeedID]; const paintedCells = state.paintedCells[cameraFeedID];
const regions = state.regionsByCamera[cameraFeedID];
const selectedRegionIndex = state.selectedRegionIndex;
const mode = state.modeByCamera[cameraFeedID];
const { latestBitmapRef, isloading } = useCreateVideoSnapshot(); const { latestBitmapRef, isloading } = useCreateVideoSnapshot();
const [stageSize, setStageSize] = useState({ width: 740, height: 460 }); const [stageSize, setStageSize] = useState({ width: 740, height: 460 });
const isDrawingRef = useRef(false); const isDrawingRef = useRef(false);

View File

@@ -103,9 +103,44 @@ export type CameraFeedState = {
B: Map<string, PaintedCell>; B: Map<string, PaintedCell>;
C: Map<string, PaintedCell>; C: Map<string, PaintedCell>;
}; };
regionsByCamera: {
A: Region[];
B: Region[];
C: Region[];
};
selectedRegionIndex: number;
modeByCamera: {
A: string;
B: string;
C: string;
}; };
export type CameraFeedAction = { tabIndex?: number;
type: string; };
payload: "A" | "B" | "C";
export type CameraFeedAction =
| {
type: "SET_CAMERA_FEED";
payload: "A" | "B" | "C";
}
| {
type: "CHANGE_MODE";
payload: { cameraFeedID: "A" | "B" | "C"; mode: string };
}
| { type: "SET_SELECTED_REGION_INDEX"; payload: number }
| {
type: "SET_SELECTED_REGION_COLOUR";
payload: { cameraFeedID: "A" | "B" | "C"; regionName: string; newColour: string };
}
| {
type: "ADD_NEW_REGION";
payload: { cameraFeedID: "A" | "B" | "C"; regionName: string; brushColour: string };
}
| {
type: "REMOVE_REGION";
payload: { cameraFeedID: "A" | "B" | "C"; regionName: string };
}
| {
type: "RESET_PAINTED_CELLS";
payload: { cameraFeedID: "A" | "B" | "C"; paintedCells: Map<string, PaintedCell> };
}; };