diff --git a/package.json b/package.json index fc3affc..25fd822 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "country-flag-icons": "^1.6.4", "formik": "^2.4.9", "konva": "^10.0.12", + "rc-slider": "^11.1.9", "react": "^19.2.0", "react-dom": "^19.2.0", "react-konva": "^19.2.1", diff --git a/src/app/providers/CameraSettingsProvider.tsx b/src/app/providers/CameraSettingsProvider.tsx index 1877a38..d1c7a8f 100644 --- a/src/app/providers/CameraSettingsProvider.tsx +++ b/src/app/providers/CameraSettingsProvider.tsx @@ -9,7 +9,6 @@ const CameraSettingsProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { if (cameraControllerQuery?.data) { - console.log(cameraControllerQuery?.data); const currentCameraControlMode = cameraControllerQuery.data.propOperationMode.value.toLowerCase(); const currentAutoMaxGain = cameraControllerQuery.data.propAutoModeMaxGain.value; const currentAutoMinShutter = cameraControllerQuery.data.propAutoModeMinShutter.value; @@ -20,11 +19,15 @@ const CameraSettingsProvider = ({ children }: { children: ReactNode }) => { const currentManualFixGain = cameraControllerQuery.data.propManualModeFixGain.value; const currentManualFixIris = cameraControllerQuery.data.propManualModeFixIris.value; + const currentShutterPriorityFixShutter = cameraControllerQuery.data.propShutterPriority.value; + const currentShutterPriorityMaxGain = cameraControllerQuery.data.propShutterPriorityMaxGain.value; + const currentShutterPriorityExposureCompensation = + cameraControllerQuery.data.propShutterPriorityExposureCompensation.value; + console.log({ - currentCameraControlMode, - currentManualFixShutter, - currentManualFixGain, - currentManualFixIris, + currentShutterPriorityFixShutter, + currentShutterPriorityMaxGain, + currentShutterPriorityExposureCompensation, }); dispatch({ type: "SET_CAMERA_CONTROLS", @@ -41,6 +44,11 @@ const CameraSettingsProvider = ({ children }: { children: ReactNode }) => { fixGain: currentManualFixGain, fixIris: currentManualFixIris, }, + shutterPriority: { + fixShutter: currentShutterPriorityFixShutter, + maxGain: currentShutterPriorityMaxGain, + exposureCompensation: currentShutterPriorityExposureCompensation, + }, }, }); } diff --git a/src/app/reducers/cameraSettingsReducer.ts b/src/app/reducers/cameraSettingsReducer.ts index 5bd19d6..c5db67e 100644 --- a/src/app/reducers/cameraSettingsReducer.ts +++ b/src/app/reducers/cameraSettingsReducer.ts @@ -8,9 +8,11 @@ export const initialState: CameraSettings = { cameraControlMode: "auto", auto: { minShutter: "1/100", maxShutter: "1/1000", maxGain: "0dB", exposureCompensation: "EC:off" }, manual: { fixShutter: "1/100", fixGain: "0dB", fixIris: "F2.0" }, + shutterPriority: { fixShutter: "1/100", maxGain: "0dB", exposureCompensation: "EC:off" }, }, regionPainter: { - paintmode: "painter", + brushSize: 1, + paintMode: "painter", paintedCells: new Map(), regions: [ { name: "Region 1", brushColour: "#FF0000" }, @@ -43,7 +45,15 @@ export const cameraSettingsReducer = (state: CameraSettings, action: CameraSetti ...state, regionPainter: { ...state.regionPainter, - paintmode: action.payload, + paintMode: action.payload, + }, + }; + case "SET_BRUSH_SIZE": + return { + ...state, + regionPainter: { + ...state.regionPainter, + brushSize: action.payload, }, }; diff --git a/src/components/ui/SliderComponent.tsx b/src/components/ui/SliderComponent.tsx new file mode 100644 index 0000000..3ed269b --- /dev/null +++ b/src/components/ui/SliderComponent.tsx @@ -0,0 +1,19 @@ +import Slider from "rc-slider"; +import "rc-slider/assets/index.css"; + +type SliderComponentProps = { + id: string; + onChange: (value: number | number[]) => void; + value?: number; + min?: number; + max?: number; + step?: number; +}; + +const SliderComponent = ({ id, onChange, value = 0, min = 0, max = 100, step = 1 }: SliderComponentProps) => { + const handleChange = (val: number | number[]) => onChange(val); + + return ; +}; + +export default SliderComponent; diff --git a/src/features/dashboard/components/videoFeed/VideoFeed.tsx b/src/features/dashboard/components/videoFeed/VideoFeed.tsx index 9eb75e5..dadc359 100644 --- a/src/features/dashboard/components/videoFeed/VideoFeed.tsx +++ b/src/features/dashboard/components/videoFeed/VideoFeed.tsx @@ -17,7 +17,6 @@ const VideoFeed = ({ mostRecentSighting, isLoading, size, modeSetting, isModal = const contextMode = cameraSettings.mode; const [localMode, setLocalMode] = useState(0); const mode = isModal ? localMode : contextMode; - console.log(mode); const { image, plateRect, plateTrack } = useCreateVideoSnapshot(mostRecentSighting); const handleModeChange = (newMode: number) => { diff --git a/src/features/setup/components/cameraControls/CameraControls.tsx b/src/features/setup/components/cameraControls/CameraControls.tsx index b745e11..060d3bc 100644 --- a/src/features/setup/components/cameraControls/CameraControls.tsx +++ b/src/features/setup/components/cameraControls/CameraControls.tsx @@ -11,7 +11,7 @@ const CameraControls = ({ state, dispatch }: CameraControlProps) => { const { cameraControllerMutation } = useCameraController(); console.log(state); const initialValues = { - cameraMode: state.cameraControlMode === "auto" ? "auto" : "manual", + cameraMode: state.cameraControlMode, auto: { minShutter: state.auto.minShutter, maxShutter: state.auto.maxShutter, @@ -23,24 +23,32 @@ const CameraControls = ({ state, dispatch }: CameraControlProps) => { fixGain: state.manual.fixGain, fixIris: state.manual.fixIris, }, + shutterPriority: { + fixShutter: state.shutterPriority.fixShutter, + maxGain: state.shutterPriority.maxGain, + exposureCompensation: state.shutterPriority.exposureCompensation, + }, }; const handleSumbit = (values: { cameraMode: string; auto: typeof initialValues.auto; manual: typeof initialValues.manual; + shutterPriority: typeof initialValues.shutterPriority; }) => { cameraControllerMutation.mutate({ - cameraControlMode: values.cameraMode as "auto" | "manual", + cameraControlMode: values.cameraMode as "auto" | "manual" | "shutter priority", auto: values.auto, manual: values.manual, + shutterPriority: values.shutterPriority, }); dispatch({ type: "SET_CAMERA_CONTROLS", payload: { - cameraControlMode: values.cameraMode as "auto" | "manual", + cameraControlMode: values.cameraMode as "auto" | "manual" | "shutter priority", auto: values.auto, manual: values.manual, + shutterPriority: values.shutterPriority, }, }); }; @@ -48,7 +56,7 @@ const CameraControls = ({ state, dispatch }: CameraControlProps) => { return ( {({ values }) => ( -
+

Controls

@@ -73,6 +81,23 @@ const CameraControls = ({ state, dispatch }: CameraControlProps) => {
+
+ +
{values.cameraMode === "auto" && (
@@ -266,6 +291,92 @@ const CameraControls = ({ state, dispatch }: CameraControlProps) => {
)} + {values.cameraMode === "shutter priority" && ( +
+
+

Shutter Speed

+
+
+ + + + + +
+
+
+ +
+

Gain

+
+
+ + + + + + + + + + + + + +
+
+
+ +
+

Exposure

+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ )} + diff --git a/src/features/setup/components/region/Region.tsx b/src/features/setup/components/region/Region.tsx index e2bfb23..866bcd4 100644 --- a/src/features/setup/components/region/Region.tsx +++ b/src/features/setup/components/region/Region.tsx @@ -1,4 +1,5 @@ import { useCameraSettingsContext } from "../../../../app/context/CameraSettingsContext"; +import SliderComponent from "../../../../components/ui/SliderComponent"; import type { CameraSettings } from "../../../../utils/types"; type RegionProps = { @@ -7,8 +8,9 @@ type RegionProps = { const Region = ({ state }: RegionProps) => { const { dispatch } = useCameraSettingsContext(); - const paintMode = state.regionPainter.paintmode; + const paintMode = state.regionPainter.paintMode; const regions = state.regionPainter.regions; + const brushSize = state.regionPainter.brushSize; const handleChangePaintMode = (event: React.ChangeEvent) => { const mode = event.target.value as "painter" | "eraser"; @@ -25,7 +27,7 @@ const Region = ({ state }: RegionProps) => { console.log(state); }; return ( -
+

Tools

@@ -64,6 +66,20 @@ const Region = ({ state }: RegionProps) => { /> Eraser + +
+ + {paintMode === "painter" ? "Brush" : "Eraser"} Size: {brushSize} + + dispatch({ type: "SET_BRUSH_SIZE", payload: value as number })} + value={brushSize} + min={1} + max={5} + step={1} + /> +
diff --git a/src/features/setup/components/videofeed/VideoFeedSetup.tsx b/src/features/setup/components/videofeed/VideoFeedSetup.tsx index 9336dfd..27090b0 100644 --- a/src/features/setup/components/videofeed/VideoFeedSetup.tsx +++ b/src/features/setup/components/videofeed/VideoFeedSetup.tsx @@ -18,7 +18,8 @@ const VideoFeedSetup = () => { const { state, dispatch } = useCameraSettingsContext(); const cameraMode = state.cameraMode; const paintedCells = state.regionPainter.paintedCells; - const paintMode = state.regionPainter.paintmode; + const paintMode = state.regionPainter.paintMode; + const brushSize = state.regionPainter.brushSize; // eslint-disable-next-line @typescript-eslint/no-explicit-any const paintLayerRef = useRef(null); const size = state.imageSize; @@ -37,43 +38,47 @@ const VideoFeedSetup = () => { }; const image = draw(latestBitmapRef); - const paintCell = (x: number, y: number) => { + const paintCell = (x: number, y: number, brushSize: number) => { const col = Math.floor(x / (cellSize + gap)); const row = Math.floor(y / (cellSize + gap)); - + const raduis = Math.floor(brushSize / 2); 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(); + for (let r = row - raduis; r <= row + raduis; r++) { + for (let c = col - raduis; c <= col + raduis; c++) { + if (r < 0 || r >= rows || c < 0 || c >= cols) continue; + const key = `${r}-${c}`; + const map = paintedCells; + const existing = map.get(key); + if (paintMode === "eraser") { + if (map.has(key)) { + map.delete(key); + paintLayerRef.current?.batchDraw(); + } + continue; + } + if (existing && existing.colour === currentColour) continue; + map.set(key, { colour: currentColour, region: activeRegion }); } - return; } - - if (existing && existing.colour === currentColour) return; - map.set(cellKey, { colour: currentColour, region: activeRegion }); paintLayerRef.current?.batchDraw(); }; const handleStageMouseDown = (e: KonvaEventObject) => { if (!(cameraMode === 1)) return; const pos = e.target.getStage()?.getPointerPosition(); - if (pos) paintCell(pos.x, pos.y); + if (pos) paintCell(pos.x, pos.y, brushSize); }; const handleMouseMove = (e: KonvaEventObject) => { if (!(cameraMode === 1)) return; const pos = e.target.getStage()?.getPointerPosition(); - if (pos && e.evt.buttons === 1) paintCell(pos.x, pos.y); + if (pos && e.evt.buttons === 1) paintCell(pos.x, pos.y, brushSize); }; useEffect(() => { diff --git a/src/features/setup/hooks/useCameraControlConfig.ts b/src/features/setup/hooks/useCameraControlConfig.ts index b9247b6..b0b3270 100644 --- a/src/features/setup/hooks/useCameraControlConfig.ts +++ b/src/features/setup/hooks/useCameraControlConfig.ts @@ -10,25 +10,30 @@ const fetchCameraControllerConfig = async () => { const updateCameraControllerConfig = async (config: CameraSettings["cameraControls"]) => { if (!config) return; - const fields = []; if (config.cameraControlMode === "auto") { fields.push( - { name: "propOperationMode", value: "Auto" }, - { name: "propAutoModeMaxGain", value: config.auto.maxGain }, - { name: "propAutoModeMinShutter", value: config.auto.minShutter }, - { name: "propAutoModeMaxShutter", value: config.auto.maxShutter }, - { name: "propAutoModeExposureCompensation", value: config.auto.exposureCompensation }, + { property: "propOperationMode", value: "Auto" }, + { property: "propAutoModeMaxGain", value: config.auto.maxGain }, + { property: "propAutoModeMinShutter", value: config.auto.minShutter }, + { property: "propAutoModeMaxShutter", value: config.auto.maxShutter }, + { property: "propAutoModeExposureCompensation", value: config.auto.exposureCompensation }, ); } else if (config.cameraControlMode === "manual") { fields.push( - { name: "propOperationMode", value: "Manual" }, - { name: "propManualModeShutter", value: config.manual.fixShutter }, - { name: "propManualModeFixGain", value: config.manual.fixGain }, - { name: "propManualModeFixIris", value: config.manual.fixIris }, + { property: "propOperationMode", value: "Manual" }, + { property: "propManualModeShutter", value: config.manual.fixShutter }, + { property: "propManualModeFixGain", value: config.manual.fixGain }, + { property: "propManualModeFixIris", value: config.manual.fixIris }, + ); + } else if (config.cameraControlMode === "shutter priority") { + fields.push( + { property: "propOperationMode", value: "Shutter Priority" }, + { property: "propShutterPriority", value: config.shutterPriority.fixShutter }, + { property: "propShutterPriorityMaxGain", value: config.shutterPriority.maxGain }, + { property: "propShutterPriorityExposureCompensation", value: config.shutterPriority.exposureCompensation }, ); } - const data = { id: "Colour--camera-control-widget-config", fields: fields, @@ -41,7 +46,6 @@ const updateCameraControllerConfig = async (config: CameraSettings["cameraContro body: JSON.stringify(data), }); if (!response.ok) throw new Error("Cannot reach camera controller endpoint"); - console.log(response); return response.json(); }; diff --git a/src/utils/types.ts b/src/utils/types.ts index dfd8a3d..b3a21bb 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -67,7 +67,7 @@ export type CameraSettings = { mode: number; imageSize: { width: number; height: number }; cameraControls: { - cameraControlMode: "auto" | "manual"; + cameraControlMode: "auto" | "manual" | "shutter priority"; auto: { minShutter: string; maxShutter: string; @@ -79,9 +79,15 @@ export type CameraSettings = { fixGain: string; fixIris: string; }; + shutterPriority: { + fixShutter: string; + maxGain: string; + exposureCompensation: string; + }; }; regionPainter: { - paintmode: "painter" | "eraser"; + brushSize: number; + paintMode: "painter" | "eraser"; paintedCells: Map; regions: Region[]; selectedRegionIndex: number; @@ -108,6 +114,10 @@ export type CameraSettingsAction = | { type: "SET_CAMERA_CONTROLS"; payload: CameraSettings["cameraControls"]; + } + | { + type: "SET_BRUSH_SIZE"; + payload: number; }; export type CameraStatus = { diff --git a/yarn.lock b/yarn.lock index eff3a7f..5cb4914 100644 --- a/yarn.lock +++ b/yarn.lock @@ -226,6 +226,11 @@ "@babel/plugin-transform-modules-commonjs" "^7.27.1" "@babel/plugin-transform-typescript" "^7.28.5" +"@babel/runtime@^7.10.1", "@babel/runtime@^7.18.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" + integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== + "@babel/template@^7.27.2": version "7.27.2" resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz" @@ -1247,6 +1252,11 @@ chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" +classnames@^2.2.5: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + clsx@^2.0.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" @@ -2019,6 +2029,23 @@ punycode@^2.1.0: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +rc-slider@^11.1.9: + version "11.1.9" + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-11.1.9.tgz#d872130fbf4ec51f28543d62e90451091d6f5208" + integrity sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-util "^5.36.0" + +rc-util@^5.36.0: + version "5.44.4" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.44.4.tgz#89ee9037683cca01cd60f1a6bbda761457dd6ba5" + integrity sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w== + dependencies: + "@babel/runtime" "^7.18.3" + react-is "^18.2.0" + react-dom@^19.2.0: version "19.2.3" resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz" @@ -2036,6 +2063,11 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + react-konva@^19.2.1: version "19.2.1" resolved "https://registry.npmjs.org/react-konva/-/react-konva-19.2.1.tgz"