- Implemented zoom mode in RegionSelector for digital zooming. - Updated VideoFeedGridPainter to handle zoom interactions. - Refactored useGetVideoFeed to support target detection and video feed queries based on mode. - Enhanced useCreateVideoSnapshot to manage snapshots during zoom mode.
276 lines
9.8 KiB
TypeScript
276 lines
9.8 KiB
TypeScript
import type { ColourData, PaintedCell, Region } from "../../../../types/types";
|
|
import ColourPicker from "./ColourPicker";
|
|
import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext";
|
|
import { useColourDectection } from "../../hooks/useColourDetection";
|
|
import { useBlackBoard } from "../../../../hooks/useBlackBoard";
|
|
import { toast } from "sonner";
|
|
|
|
type RegionSelectorProps = {
|
|
regions: Region[];
|
|
selectedRegionIndex: number;
|
|
mode: string;
|
|
cameraFeedID: "A" | "B" | "C";
|
|
isResetAllModalOpen: boolean;
|
|
handleClose: () => void;
|
|
setIsResetModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
};
|
|
|
|
const RegionSelector = ({
|
|
regions,
|
|
selectedRegionIndex,
|
|
mode,
|
|
cameraFeedID,
|
|
isResetAllModalOpen,
|
|
|
|
setIsResetModalOpen,
|
|
}: RegionSelectorProps) => {
|
|
const { colourMutation } = useColourDectection();
|
|
const { state, dispatch } = useCameraFeedContext();
|
|
const { blackboardMutation } = useBlackBoard();
|
|
const paintedCells = state.paintedCells[cameraFeedID];
|
|
|
|
const handleChange = (e: { target: { value: string } }) => {
|
|
dispatch({ type: "CHANGE_MODE", payload: { cameraFeedID: cameraFeedID, mode: e.target.value } });
|
|
};
|
|
|
|
const handleResetRegion = () => {
|
|
dispatch({
|
|
type: "RESET_PAINTED_CELLS",
|
|
payload: { cameraFeedID: cameraFeedID, paintedCells: new Map<string, PaintedCell>() },
|
|
});
|
|
};
|
|
|
|
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 },
|
|
});
|
|
};
|
|
|
|
const handleAddRegionClick = () => {
|
|
const regionName = `Bay ${regions.length + 1}`;
|
|
dispatch({
|
|
type: "ADD_NEW_REGION",
|
|
payload: { cameraFeedID: cameraFeedID, regionName: regionName, brushColour: "#ffffff" },
|
|
});
|
|
};
|
|
|
|
const handleRemoveClick = () => {
|
|
dispatch({
|
|
type: "REMOVE_REGION",
|
|
payload: { cameraFeedID: cameraFeedID, regionName: regions[selectedRegionIndex].name },
|
|
});
|
|
};
|
|
|
|
const openResetModal = () => {
|
|
if (isResetAllModalOpen) return;
|
|
setIsResetModalOpen(true);
|
|
};
|
|
|
|
const handleSaveclick = () => {
|
|
const regions: ColourData[] = [];
|
|
const test = Array.from(paintedCells.entries());
|
|
const region1 = test.filter(([, cell]) => cell.region.name === "Bay 1");
|
|
const region2 = test.filter(([, cell]) => cell.region.name === "Bay 2");
|
|
const region3 = test.filter(([, cell]) => cell.region.name === "Bay 3");
|
|
const region4 = test.filter(([, cell]) => cell.region.name === "Bay 4");
|
|
const region5 = test.filter(([, cell]) => cell.region.name === "Bay 5");
|
|
const region1Data = {
|
|
id: 1,
|
|
cells: region1.map(([key]) => [parseInt(key.split("-")[1]), parseInt(key.split("-")[0])]),
|
|
};
|
|
const region2Data = {
|
|
id: 2,
|
|
cells: region2.map(([key]) => [parseInt(key.split("-")[1]), parseInt(key.split("-")[0])]),
|
|
};
|
|
const region3Data = {
|
|
id: 3,
|
|
cells: region3.map(([key]) => [parseInt(key.split("-")[1]), parseInt(key.split("-")[0])]),
|
|
};
|
|
const region4Data = {
|
|
id: 4,
|
|
cells: region4.map(([key]) => [parseInt(key.split("-")[1]), parseInt(key.split("-")[0])]),
|
|
};
|
|
const region5Data = {
|
|
id: 5,
|
|
cells: region5.map(([key]) => [parseInt(key.split("-")[1]), parseInt(key.split("-")[0])]),
|
|
};
|
|
if (region1Data.cells.length > 0) {
|
|
regions.push(region1Data);
|
|
}
|
|
if (region2Data.cells.length > 0) {
|
|
regions.push(region2Data);
|
|
}
|
|
if (region3Data.cells.length > 0) {
|
|
regions.push(region3Data);
|
|
}
|
|
if (region4Data.cells.length > 0) {
|
|
regions.push(region4Data);
|
|
}
|
|
if (region5Data.cells.length > 0) {
|
|
regions.push(region5Data);
|
|
}
|
|
|
|
colourMutation.mutate({ cameraFeedID, regions: regions });
|
|
|
|
// Convert Map to plain object for blackboard
|
|
const serializableState = {
|
|
...state,
|
|
paintedCells: {
|
|
A: Array.from(state.paintedCells.A.entries()),
|
|
B: Array.from(state.paintedCells.B.entries()),
|
|
C: Array.from(state.paintedCells.C.entries()),
|
|
},
|
|
};
|
|
blackboardMutation.mutate({ operation: "INSERT", path: `cameraFeed`, value: serializableState });
|
|
toast.success("Region data saved successfully!");
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4 max-h-[50%]">
|
|
<div className="flex flex-col md:flex-row gap-3">
|
|
<div className="p-2 border border-gray-600 rounded-lg flex flex-col h-[10%] w-full">
|
|
<h2 className="text-2xl mb-2">Tools</h2>
|
|
<div className="flex flex-col">
|
|
<label
|
|
htmlFor="paintMode"
|
|
className={`p-4 border rounded-lg mb-2
|
|
${mode === "painter" ? "border-gray-400 bg-[#202b36]" : "bg-[#253445] border-gray-700"}
|
|
hover:bg-[#202b36] hover:cursor-pointer`}
|
|
>
|
|
<input
|
|
id="paintMode"
|
|
type="radio"
|
|
onChange={handleChange}
|
|
checked={mode === "painter"}
|
|
value="painter"
|
|
className="sr-only"
|
|
/>
|
|
<span className="text-xl">Paint mode</span>
|
|
</label>
|
|
<label
|
|
htmlFor="eraseMode"
|
|
className={`p-4 border rounded-lg mb-2
|
|
${mode === "eraser" ? "border-gray-400 bg-[#202b36]" : "bg-[#253445] border-gray-700"}
|
|
hover:bg-[#202b36] hover:cursor-pointer`}
|
|
>
|
|
<input
|
|
id="eraseMode"
|
|
type="radio"
|
|
onChange={handleChange}
|
|
checked={mode === "eraser"}
|
|
value={"eraser"}
|
|
className="sr-only"
|
|
/>
|
|
<span className="text-xl">Erase mode</span>
|
|
</label>
|
|
<label
|
|
htmlFor="zoomMode"
|
|
className={`p-4 border rounded-lg mb-2
|
|
${mode === "zoom" ? "border-gray-400 bg-[#202b36]" : "bg-[#253445] border-gray-700"}
|
|
hover:bg-[#202b36] hover:cursor-pointer`}
|
|
>
|
|
<input
|
|
id="zoomMode"
|
|
type="radio"
|
|
onChange={handleChange}
|
|
checked={mode === "zoom"}
|
|
value="zoom"
|
|
className="sr-only"
|
|
/>
|
|
<div className="flex flex-col space-y-3">
|
|
<span className="text-xl">Enlarge image</span>
|
|
{mode === "zoom" && (
|
|
<small className={`text-gray-400 italic`}>Use mouse to digitally zoom in and out</small>
|
|
)}
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-2 border border-gray-600 rounded-lg flex flex-col w-full">
|
|
<h2 className="text-2xl mb-2">Bay Select</h2>
|
|
<>
|
|
{regions?.map((region, idx) => {
|
|
const isSelected = selectedRegionIndex === idx;
|
|
const inputId = `region-${idx}`;
|
|
return (
|
|
<label
|
|
htmlFor={inputId}
|
|
key={region.name}
|
|
className={`items-center p-4 m-1 rounded-xl border flex flex-row justify-between
|
|
${isSelected ? "border-gray-400 bg-[#202b36]" : "bg-[#253445] border-gray-700"} hover:bg-[#202b36] hover:cursor-pointer`}
|
|
>
|
|
<div className="flex flex-row gap-4 items-center">
|
|
<input
|
|
type="radio"
|
|
checked={isSelected}
|
|
id={inputId}
|
|
name="region"
|
|
className="sr-only"
|
|
onChange={() => {
|
|
handleModeChange("painter");
|
|
handleRegionSelect(idx);
|
|
}}
|
|
/>
|
|
<span className="text-xl">{region.name}</span>
|
|
</div>
|
|
<ColourPicker
|
|
colour={region.brushColour}
|
|
setColour={(c: string) => handleRegionColourChange(idx, c)}
|
|
/>
|
|
<div></div>
|
|
</label>
|
|
);
|
|
})}
|
|
</>
|
|
<div className="flex flex-col gap-4 mt-4">
|
|
<button className="border border-blue-900 bg-blue-700 px-4 py-1 rounded-md" onClick={handleAddRegionClick}>
|
|
Add Bay
|
|
</button>
|
|
<button className="border border-red-900 bg-red-700 px-4 py-1 rounded-md" onClick={handleRemoveClick}>
|
|
Remove Bay
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-2 border border-gray-600 rounded-lg flex flex-col w-full">
|
|
<h2 className="text-2xl mb-2">Actions</h2>
|
|
<div className="flex flex-col md:flex-row mx-auto gap-4 justify-center">
|
|
<button
|
|
onClick={handleSaveclick}
|
|
className="mt-2 px-4 py-2 border border-blue-600 rounded-md text-white bg-blue-600 w-full md:w-full hover:bg-blue-700 hover:cursor-pointer"
|
|
>
|
|
Save Region
|
|
</button>
|
|
<button
|
|
onClick={handleResetRegion}
|
|
className="mt-2 px-4 py-2 border border-red-600 rounded-md text-white bg-red-600 w-full md:w-full hover:bg-red-700 hover:cursor-pointer"
|
|
>
|
|
Reset Region
|
|
</button>
|
|
<button
|
|
onClick={openResetModal}
|
|
className="mt-2 px-4 py-2 border border-red-600 rounded-md text-white bg-red-600 w-full md:w-full hover:bg-red-700 hover:cursor-pointer"
|
|
>
|
|
Reset All
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RegionSelector;
|