- added region painting context and components
- can switch to target detection on region select
This commit is contained in:
@@ -1,19 +1,28 @@
|
|||||||
import type { CameraSettings, CameraSettingsAction } from "../../utils/types";
|
import type { CameraSettings, CameraSettingsAction } from "../../utils/types";
|
||||||
|
|
||||||
export const initialState: CameraSettings = {
|
export const initialState: CameraSettings = {
|
||||||
|
cameraMode: 0,
|
||||||
mode: 0,
|
mode: 0,
|
||||||
imageSize: { width: 1280, height: 960 },
|
imageSize: { width: 1280, height: 960 },
|
||||||
regionPainter: {
|
regionPainter: {
|
||||||
paintedCells: [],
|
paintmode: "painter",
|
||||||
|
paintedCells: new Map(),
|
||||||
regions: [
|
regions: [
|
||||||
{ name: "Region 1", brushColour: "#FF0000", cells: [] },
|
{ name: "Region 1", brushColour: "#FF0000" },
|
||||||
{ name: "Region 2", brushColour: "#00FF00", cells: [] },
|
{ name: "Region 2", brushColour: "#00FF00" },
|
||||||
|
{ name: "Region 3", brushColour: "#0000FF" },
|
||||||
],
|
],
|
||||||
|
selectedRegionIndex: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cameraSettingsReducer = (state: CameraSettings, action: CameraSettingsAction) => {
|
export const cameraSettingsReducer = (state: CameraSettings, action: CameraSettingsAction) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case "SET_CAMERA_MODE":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
cameraMode: action.payload,
|
||||||
|
};
|
||||||
case "SET_MODE":
|
case "SET_MODE":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -24,6 +33,23 @@ export const cameraSettingsReducer = (state: CameraSettings, action: CameraSetti
|
|||||||
...state,
|
...state,
|
||||||
imageSize: action.payload,
|
imageSize: action.payload,
|
||||||
};
|
};
|
||||||
|
case "SET_REGION_PAINTMODE":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
regionPainter: {
|
||||||
|
...state.regionPainter,
|
||||||
|
paintmode: action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
case "SET_SELECTED_REGION_INDEX":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
regionPainter: {
|
||||||
|
...state.regionPainter,
|
||||||
|
selectedRegionIndex: action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Formik, Form } from "formik";
|
import { Formik, Form } from "formik";
|
||||||
|
import type { CameraSettings } from "../../../../utils/types";
|
||||||
|
|
||||||
type CameraControlProps = {
|
type CameraControlProps = {
|
||||||
tabIndex: number;
|
state: CameraSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CameraControls = ({ tabIndex }: CameraControlProps) => {
|
const CameraControls = ({ state }: CameraControlProps) => {
|
||||||
|
console.log(state);
|
||||||
const initialValues = {};
|
const initialValues = {};
|
||||||
|
|
||||||
console.log(tabIndex);
|
|
||||||
const handleSumbit = (values: { test?: string }) => {
|
const handleSumbit = (values: { test?: string }) => {
|
||||||
console.log(values);
|
console.log(values);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,24 +3,32 @@ import "react-tabs/style/react-tabs.css";
|
|||||||
import Card from "../../../../components/ui/Card";
|
import Card from "../../../../components/ui/Card";
|
||||||
import CameraControls from "../cameraControls/CameraControls";
|
import CameraControls from "../cameraControls/CameraControls";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Region from "../region/Region";
|
||||||
|
import { useCameraSettingsContext } from "../../../../app/context/CameraSettingsContext";
|
||||||
|
|
||||||
const CameraSetup = () => {
|
const CameraSetup = () => {
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
console.log(tabIndex);
|
const { state, dispatch } = useCameraSettingsContext();
|
||||||
return (
|
return (
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<Tabs selectedIndex={tabIndex} onSelect={(index) => setTabIndex(index)}>
|
<Tabs
|
||||||
|
selectedIndex={tabIndex}
|
||||||
|
onSelect={(index) => {
|
||||||
|
dispatch({ type: "SET_CAMERA_MODE", payload: index });
|
||||||
|
setTabIndex(index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab>Camera</Tab>
|
<Tab>Camera</Tab>
|
||||||
<Tab>Regions</Tab>
|
<Tab>Target Detection</Tab>
|
||||||
<Tab>Crop</Tab>
|
<Tab>Crop</Tab>
|
||||||
<Tab>Advanced</Tab>
|
<Tab>Advanced</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<CameraControls tabIndex={tabIndex} />
|
<CameraControls state={state} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<div>Regions</div>
|
<Region state={state} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<div>Crop</div>
|
<div>Crop</div>
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ const PlatePatchSetup = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{sightingList?.map((sighting) => (
|
{sightingList?.map((sighting, index) => (
|
||||||
<tr key={sighting.vrm} className="border-b border-gray-700/50 hover:bg-gray-700/20">
|
<tr key={index} className="border-b border-gray-700/50 hover:bg-gray-700/20">
|
||||||
<td className="px-4 py-3 font-mono text-lg">{sighting.vrm}</td>
|
<td className="px-4 py-3 font-mono text-lg">{sighting.vrm}</td>
|
||||||
<td className="px-4 py-3 text-center">{sighting.seenCount}</td>
|
<td className="px-4 py-3 text-center">{sighting.seenCount}</td>
|
||||||
<td className="px-4 py-3">{sighting.timeStamp}</td>
|
<td className="px-4 py-3">{sighting.timeStamp}</td>
|
||||||
|
|||||||
118
src/features/setup/components/region/Region.tsx
Normal file
118
src/features/setup/components/region/Region.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { useCameraSettingsContext } from "../../../../app/context/CameraSettingsContext";
|
||||||
|
import type { CameraSettings } from "../../../../utils/types";
|
||||||
|
|
||||||
|
type RegionProps = {
|
||||||
|
state: CameraSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Region = ({ state }: RegionProps) => {
|
||||||
|
const { dispatch } = useCameraSettingsContext();
|
||||||
|
const paintMode = state.regionPainter.paintmode;
|
||||||
|
const regions = state.regionPainter.regions;
|
||||||
|
|
||||||
|
const handleChangePaintMode = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const mode = event.target.value as "painter" | "eraser";
|
||||||
|
dispatch({ type: "SET_REGION_PAINTMODE", payload: mode });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePaintMode = (mode: "painter" | "eraser") => dispatch({ type: "SET_REGION_PAINTMODE", payload: mode });
|
||||||
|
|
||||||
|
const handleChangeRegion = (idx: number) => {
|
||||||
|
dispatch({ type: "SET_SELECTED_REGION_INDEX", payload: idx });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveClick = () => {
|
||||||
|
console.log(state);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex flex-col gap-4 md:flex-row">
|
||||||
|
<div className="border border-gray-600 p-2 rounded-lg 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
|
||||||
|
${paintMode === "painter" ? "border-gray-400 bg-[#202b36]" : "bg-[#253445] border-gray-700"}
|
||||||
|
hover:bg-[#202b36] hover:cursor-pointer`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="paintMode"
|
||||||
|
name="paintMode"
|
||||||
|
onChange={handleChangePaintMode}
|
||||||
|
checked={paintMode === "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
|
||||||
|
${paintMode === "eraser" ? "border-gray-400 bg-[#202b36]" : "bg-[#253445] border-gray-700"}
|
||||||
|
hover:bg-[#202b36] hover:cursor-pointer`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="eraseMode"
|
||||||
|
name="paintMode"
|
||||||
|
onChange={handleChangePaintMode}
|
||||||
|
checked={paintMode === "eraser"}
|
||||||
|
value="eraser"
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<span className="text-xl">Eraser</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border border-gray-600 p-2 rounded-lg w-full">
|
||||||
|
<h2 className="text-2xl mb-2">Regions (Lanes)</h2>
|
||||||
|
<div>
|
||||||
|
{regions.map((region, 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
|
||||||
|
${state.regionPainter.selectedRegionIndex === idx ? "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"
|
||||||
|
id={inputID}
|
||||||
|
checked={state.regionPainter.selectedRegionIndex === idx}
|
||||||
|
name="region"
|
||||||
|
className="sr-only"
|
||||||
|
onChange={() => {
|
||||||
|
handlePaintMode("painter");
|
||||||
|
handleChangeRegion(idx);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-lg">{region.name}</span>
|
||||||
|
<div className="w-6 h-6 rounded mt-1" style={{ backgroundColor: region.brushColour }}></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border border-gray-600 rounded-lg p-4 flex flex-col">
|
||||||
|
<h2 className="text-xl">Actions</h2>
|
||||||
|
<div className="flex flex-col justify-between w-full mt-4">
|
||||||
|
<button
|
||||||
|
className="p-2 rounded-lg bg-blue-500 text-white w-[40%] hover:bg-blue-800 cursor-pointer"
|
||||||
|
onClick={handleSaveClick}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button className="p-2 rounded-lg bg-gray-500 text-white w-[40%] mt-2">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Region;
|
||||||
@@ -1,12 +1,31 @@
|
|||||||
import { Stage, Layer, Image } from "react-konva";
|
import { Stage, Layer, Image, Shape } from "react-konva";
|
||||||
import { useCreateVideoPreviewSnapshot } from "../../hooks/useCreatePreviewImage";
|
import { useCreateVideoPreviewSnapshot } from "../../hooks/useCreatePreviewImage";
|
||||||
import { useEffect, type RefObject } from "react";
|
import { useEffect, useRef, type RefObject } from "react";
|
||||||
import { useCameraSettingsContext } from "../../../../app/context/CameraSettingsContext";
|
import { useCameraSettingsContext } from "../../../../app/context/CameraSettingsContext";
|
||||||
|
import type { KonvaEventObject } from "konva/lib/Node";
|
||||||
|
|
||||||
|
const BACKEND_WIDTH = 640;
|
||||||
|
|
||||||
|
const BACKEND_CELL_SIZE = 16;
|
||||||
|
|
||||||
|
const rows = 22.5;
|
||||||
|
const cols = 40;
|
||||||
|
const gap = 0;
|
||||||
|
|
||||||
const VideoFeedSetup = () => {
|
const VideoFeedSetup = () => {
|
||||||
const { latestBitmapRef, isLoading } = useCreateVideoPreviewSnapshot();
|
const { latestBitmapRef, isLoading } = useCreateVideoPreviewSnapshot();
|
||||||
const { state, dispatch } = useCameraSettingsContext();
|
const { state, dispatch } = useCameraSettingsContext();
|
||||||
|
const cameraMode = state.cameraMode;
|
||||||
|
const paintedCells = state.regionPainter.paintedCells;
|
||||||
|
const paintMode = state.regionPainter.paintmode;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const paintLayerRef = useRef<any>(null);
|
||||||
const size = state.imageSize;
|
const size = state.imageSize;
|
||||||
|
const selectedRegionIndex = state.regionPainter.selectedRegionIndex;
|
||||||
|
const region = state.regionPainter.regions[selectedRegionIndex];
|
||||||
|
|
||||||
|
const currentScale = size.width / BACKEND_WIDTH;
|
||||||
|
const cellSize = BACKEND_CELL_SIZE * currentScale;
|
||||||
|
|
||||||
const draw = (bmp: RefObject<ImageBitmap | null>): ImageBitmap | null => {
|
const draw = (bmp: RefObject<ImageBitmap | null>): ImageBitmap | null => {
|
||||||
if (!bmp || !bmp.current) {
|
if (!bmp || !bmp.current) {
|
||||||
@@ -17,6 +36,45 @@ const VideoFeedSetup = () => {
|
|||||||
};
|
};
|
||||||
const image = draw(latestBitmapRef);
|
const image = draw(latestBitmapRef);
|
||||||
|
|
||||||
|
const paintCell = (x: number, y: number) => {
|
||||||
|
const col = Math.floor(x / (cellSize + gap));
|
||||||
|
const row = Math.floor(y / (cellSize + gap));
|
||||||
|
|
||||||
|
if (row < 0 || row >= rows || col < 0 || col >= cols) return;
|
||||||
|
|
||||||
|
const activeRegion = region;
|
||||||
|
if (!activeRegion) return;
|
||||||
|
const cellKey = `${row}-${col}`;
|
||||||
|
const currentColour = region.brushColour;
|
||||||
|
|
||||||
|
const map = paintedCells;
|
||||||
|
const existing = map.get(cellKey);
|
||||||
|
|
||||||
|
if (paintMode === "eraser") {
|
||||||
|
if (map.has(cellKey)) {
|
||||||
|
map.delete(cellKey);
|
||||||
|
paintLayerRef.current?.batchDraw();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing && existing.colour === currentColour) return;
|
||||||
|
map.set(cellKey, { colour: currentColour, region: activeRegion });
|
||||||
|
paintLayerRef.current?.batchDraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStageMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
if (!(cameraMode === 1)) return;
|
||||||
|
const pos = e.target.getStage()?.getPointerPosition();
|
||||||
|
if (pos) paintCell(pos.x, pos.y);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
if (!(cameraMode === 1)) return;
|
||||||
|
const pos = e.target.getStage()?.getPointerPosition();
|
||||||
|
if (pos && e.evt.buttons === 1) paintCell(pos.x, pos.y);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateSize = () => {
|
const updateSize = () => {
|
||||||
const width = window.innerWidth * 0.48;
|
const width = window.innerWidth * 0.48;
|
||||||
@@ -31,8 +89,35 @@ const VideoFeedSetup = () => {
|
|||||||
if (isLoading) return <>Loading...</>;
|
if (isLoading) return <>Loading...</>;
|
||||||
return (
|
return (
|
||||||
<div className="mt-[1%]">
|
<div className="mt-[1%]">
|
||||||
<Stage width={size.width} height={size.height}>
|
<Stage width={size.width} height={size.height} onMouseDown={handleStageMouseDown} onMouseMove={handleMouseMove}>
|
||||||
<Layer>{image && <Image image={image} height={size.height} width={size.width} cornerRadius={10} />}</Layer>
|
<Layer>{image && <Image image={image} height={size.height} width={size.width} cornerRadius={10} />}</Layer>
|
||||||
|
<Layer ref={paintLayerRef} opacity={0.6}>
|
||||||
|
{cameraMode === 1 && (
|
||||||
|
<Shape
|
||||||
|
sceneFunc={(ctx, shape) => {
|
||||||
|
const cells = paintedCells;
|
||||||
|
if (!cells || cells.size === 0 || !paintLayerRef.current) return;
|
||||||
|
cells?.forEach((cell, key) => {
|
||||||
|
const [rowStr, colStr] = key.split("-");
|
||||||
|
const row = Number(rowStr);
|
||||||
|
const col = Number(colStr);
|
||||||
|
|
||||||
|
const x = col * (cellSize + gap);
|
||||||
|
const y = row * (cellSize + gap);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(x, y, cellSize, cellSize);
|
||||||
|
ctx.fillStyle = cell.colour;
|
||||||
|
ctx.fill();
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.fillStrokeShape(shape);
|
||||||
|
}}
|
||||||
|
width={size.width}
|
||||||
|
height={size.height}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Layer>
|
||||||
</Stage>
|
</Stage>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useVideoPreview } from "./useVideoPreview";
|
import { useVideoPreview } from "./useVideoPreview";
|
||||||
|
import { useCameraSettingsContext } from "../../../app/context/CameraSettingsContext";
|
||||||
|
|
||||||
export const useCreateVideoPreviewSnapshot = () => {
|
export const useCreateVideoPreviewSnapshot = () => {
|
||||||
const { videoPreviewQuery } = useVideoPreview();
|
const { state } = useCameraSettingsContext();
|
||||||
|
const { videoPreviewQuery, targetDetectionFeedQuery } = useVideoPreview(state.cameraMode);
|
||||||
const latestBitmapRef = useRef<ImageBitmap | null>(null);
|
const latestBitmapRef = useRef<ImageBitmap | null>(null);
|
||||||
const isLoading = videoPreviewQuery?.isPending;
|
const isLoading = videoPreviewQuery?.isPending || targetDetectionFeedQuery?.isPending;
|
||||||
const imageBlob = videoPreviewQuery?.data;
|
let snapshot;
|
||||||
|
if (state.cameraMode === 0) {
|
||||||
|
snapshot = videoPreviewQuery?.data;
|
||||||
|
} else if (state.cameraMode === 1) {
|
||||||
|
snapshot = targetDetectionFeedQuery?.data;
|
||||||
|
}
|
||||||
|
const imageBlob = snapshot;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function createImageBitmapFromBlob() {
|
async function createImageBitmapFromBlob() {
|
||||||
if (!imageBlob) return;
|
if (!imageBlob) return;
|
||||||
|
|||||||
@@ -9,11 +9,27 @@ const fetchVideoPreview = async () => {
|
|||||||
return response.blob();
|
return response.blob();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useVideoPreview = () => {
|
const fetchTargetDectionFeed = async () => {
|
||||||
|
const response = await fetch(`${cambase}/TargetDetectionColour-preview`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch target detection feed");
|
||||||
|
}
|
||||||
|
return response.blob();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useVideoPreview = (mode: number) => {
|
||||||
const videoPreviewQuery = useQuery({
|
const videoPreviewQuery = useQuery({
|
||||||
queryKey: ["videoPreview"],
|
queryKey: ["videoPreview"],
|
||||||
queryFn: fetchVideoPreview,
|
queryFn: fetchVideoPreview,
|
||||||
refetchInterval: 100,
|
refetchInterval: 100,
|
||||||
|
enabled: mode === 0,
|
||||||
});
|
});
|
||||||
return { videoPreviewQuery };
|
|
||||||
|
const targetDetectionFeedQuery = useQuery({
|
||||||
|
queryKey: ["targetDetectionFeed"],
|
||||||
|
queryFn: fetchTargetDectionFeed,
|
||||||
|
refetchInterval: 100,
|
||||||
|
enabled: mode === 1,
|
||||||
|
});
|
||||||
|
return { videoPreviewQuery, targetDetectionFeedQuery };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,12 +52,25 @@ export type NpedJSON = {
|
|||||||
"INSURANCE STATUS": string;
|
"INSURANCE STATUS": string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Region = {
|
||||||
|
name: string;
|
||||||
|
brushColour: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PaintedCell = {
|
||||||
|
colour: string;
|
||||||
|
region: Region;
|
||||||
|
};
|
||||||
|
|
||||||
export type CameraSettings = {
|
export type CameraSettings = {
|
||||||
|
cameraMode: number;
|
||||||
mode: number;
|
mode: number;
|
||||||
imageSize: { width: number; height: number };
|
imageSize: { width: number; height: number };
|
||||||
regionPainter: {
|
regionPainter: {
|
||||||
paintedCells: { x: number; y: number }[];
|
paintmode: "painter" | "eraser";
|
||||||
regions: { name: string; brushColour: string; cells: { x: number; y: number }[] }[];
|
paintedCells: Map<string, PaintedCell>;
|
||||||
|
regions: Region[];
|
||||||
|
selectedRegionIndex: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type CameraSettingsAction =
|
export type CameraSettingsAction =
|
||||||
@@ -68,7 +81,16 @@ export type CameraSettingsAction =
|
|||||||
| {
|
| {
|
||||||
type: "SET_IMAGE_SIZE";
|
type: "SET_IMAGE_SIZE";
|
||||||
payload: { width: number; height: number };
|
payload: { width: number; height: number };
|
||||||
};
|
}
|
||||||
|
| {
|
||||||
|
type: "SET_CAMERA_MODE";
|
||||||
|
payload: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "SET_REGION_PAINTMODE";
|
||||||
|
payload: "painter" | "eraser";
|
||||||
|
}
|
||||||
|
| { type: "SET_SELECTED_REGION_INDEX"; payload: number };
|
||||||
|
|
||||||
export type CameraStatus = {
|
export type CameraStatus = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user