diff --git a/src/app/config/wsconfig.ts b/src/app/config/wsconfig.ts index 74c8421..d6fa33d 100644 --- a/src/app/config/wsconfig.ts +++ b/src/app/config/wsconfig.ts @@ -1,5 +1,5 @@ export const wsConfig = { - infoBar: "ws://100.115.148.59/websocket-infobar", + infoBar: "ws://100.115.125.56/websocket-infobar", }; export type SocketKey = keyof typeof wsConfig; diff --git a/src/app/context/CameraFeedContext.ts b/src/app/context/CameraFeedContext.ts new file mode 100644 index 0000000..0373a14 --- /dev/null +++ b/src/app/context/CameraFeedContext.ts @@ -0,0 +1,16 @@ +import { createContext, useContext } from "react"; +import type { CameraFeedAction, CameraFeedState } from "../../types/types"; + +type CameraFeedContextType = { + state: CameraFeedState; + // check and refactor + dispatch: (state: CameraFeedAction) => void; +}; + +export const CameraFeedContext = createContext(null); + +export const useCameraFeedContext = () => { + const ctx = useContext(CameraFeedContext); + if (!ctx) throw new Error("useCameraFeedContext must be used inside "); + return ctx; +}; diff --git a/src/app/providers/AppProviders.tsx b/src/app/providers/AppProviders.tsx index 7a73155..90672e7 100644 --- a/src/app/providers/AppProviders.tsx +++ b/src/app/providers/AppProviders.tsx @@ -1,11 +1,14 @@ import type { PropsWithChildren } from "react"; import { QueryProvider } from "./QueryProviders"; import { WebSocketProvider } from "./WebSocketProvider"; +import { CameraFeedProvider } from "./CameraFeedProvider"; export const AppProviders = ({ children }: PropsWithChildren) => { return ( - {children} + + {children} + ); }; diff --git a/src/app/providers/CameraFeedProvider.tsx b/src/app/providers/CameraFeedProvider.tsx new file mode 100644 index 0000000..b983b8e --- /dev/null +++ b/src/app/providers/CameraFeedProvider.tsx @@ -0,0 +1,9 @@ +import { useReducer, type ReactNode } from "react"; +import { CameraFeedContext } from "../context/CameraFeedContext"; +import { initialState, reducer } from "../reducers/cameraFeedReducer"; + +export const CameraFeedProvider = ({ children }: { children: ReactNode }) => { + const [state, dispatch] = useReducer(reducer, initialState); + + return {children}; +}; diff --git a/src/app/reducers/cameraFeedReducer.ts b/src/app/reducers/cameraFeedReducer.ts new file mode 100644 index 0000000..eca19d0 --- /dev/null +++ b/src/app/reducers/cameraFeedReducer.ts @@ -0,0 +1,95 @@ +import type { CameraFeedAction, CameraFeedState, PaintedCell } from "../../types/types"; + +export const initialState: CameraFeedState = { + cameraFeedID: "A", + paintedCells: { + A: new Map(), + B: new Map(), + C: new Map(), + }, + 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) { + switch (action.type) { + case "SET_CAMERA_FEED": + return { + ...state, + 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(), + }, + }; + + default: + return state; + } +} diff --git a/src/features/cameras/components/CameraGrid.tsx b/src/features/cameras/components/CameraGrid.tsx index 649f8a3..fc430aa 100644 --- a/src/features/cameras/components/CameraGrid.tsx +++ b/src/features/cameras/components/CameraGrid.tsx @@ -1,52 +1,17 @@ -import { useRef, useState } from "react"; +import { useState } from "react"; import VideoFeedGridPainter from "./Video/VideoFeedGridPainter"; import CameraSettings from "./CameraSettings/CameraSettings"; -import type { PaintedCell, Region } from "../../../types/types"; + import PlatePatch from "./PlatePatch/PlatePatch"; const CameraGrid = () => { - const [regions, setRegions] = useState([ - { 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 updateRegionColour = (index: number, newColour: string) => { - setRegions((prev) => prev.map((r, i) => (i === index ? { ...r, brushColour: newColour } : r))); - }; - - const paintedCellsRef = useRef>(new Map()); - return (
- - { - 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)); - }} - /> + +
); diff --git a/src/features/cameras/components/CameraSettings/CameraPanel.tsx b/src/features/cameras/components/CameraSettings/CameraPanel.tsx new file mode 100644 index 0000000..bacfc8b --- /dev/null +++ b/src/features/cameras/components/CameraSettings/CameraPanel.tsx @@ -0,0 +1,61 @@ +import { Tabs, Tab, TabList, TabPanel } from "react-tabs"; +import { useEffect } from "react"; +import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; +import RegionSelector from "./RegionSelector"; + +type CameraPanelProps = { + tabIndex: number; +}; + +const CameraPanel = ({ tabIndex }: CameraPanelProps) => { + const { state, dispatch } = useCameraFeedContext(); + const cameraFeedID = state.cameraFeedID; + const regions = state.regionsByCamera[cameraFeedID]; + + const selectedRegionIndex = state.selectedRegionIndex; + const mode = state.modeByCamera[cameraFeedID]; + + useEffect(() => { + const mapIndextoCameraId = () => { + switch (tabIndex) { + case 0: + return "A"; + case 1: + return "B"; + case 2: + return "C"; + default: + return "A"; + } + }; + + const cameraId = mapIndextoCameraId(); + dispatch({ type: "SET_CAMERA_FEED", payload: cameraId }); + }, [dispatch, tabIndex]); + + return ( + + + Target Detection + Camera Controls + + + + + + +
+

Camera Controls

+

Controls for camera {cameraFeedID} will go here.

+
+
+
+ ); +}; + +export default CameraPanel; diff --git a/src/features/cameras/components/CameraSettings/CameraSettings.tsx b/src/features/cameras/components/CameraSettings/CameraSettings.tsx index d7f1593..b36f5f7 100644 --- a/src/features/cameras/components/CameraSettings/CameraSettings.tsx +++ b/src/features/cameras/components/CameraSettings/CameraSettings.tsx @@ -1,37 +1,14 @@ import Card from "../../../../ui/Card"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; 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"; 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; tabIndex: number; - paintedCells: RefObject>; - onAddRegion: () => void; - OnRemoveRegion: () => void; }; -const CameraSettings = ({ - regions, - selectedRegionIndex, - onSelectRegion, - onChangeRegionColour, - mode, - onSelectMode, - tabIndex, - setTabIndex, - paintedCells, - onAddRegion, - OnRemoveRegion, -}: CameraSettingsProps) => { +const CameraSettings = ({ tabIndex, setTabIndex }: CameraSettingsProps) => { return ( setTabIndex(index)} > - Target Detection - Camera 1 - Camera 2 - Camera 3 + Camera A + Camera B + Camera C - + -
Camera details {tabIndex}
+
-
Camera details {tabIndex}
-
- -
Camera details {tabIndex}
+
diff --git a/src/features/cameras/components/CameraSettings/RegionSelector.tsx b/src/features/cameras/components/CameraSettings/RegionSelector.tsx index bddbf2e..6918f2f 100644 --- a/src/features/cameras/components/CameraSettings/RegionSelector.tsx +++ b/src/features/cameras/components/CameraSettings/RegionSelector.tsx @@ -1,45 +1,57 @@ -import ColourPicker from "./ColourPicker"; import type { PaintedCell, Region } from "../../../../types/types"; -import type { RefObject } from "react"; +import ColourPicker from "./ColourPicker"; +import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; type RegionSelectorProps = { regions: Region[]; selectedRegionIndex: number; - onSelectRegion: (index: number) => void; - onChangeRegionColour: (index: number, colour: string) => void; mode: string; - onSelectMode: (mode: string) => void; - paintedCells: RefObject>; - onAddRegion: () => void; - OnRemoveRegion: () => void; + cameraFeedID: "A" | "B" | "C"; }; -const RegionSelector = ({ - regions, - selectedRegionIndex, - onSelectRegion, - onChangeRegionColour, - mode, - onSelectMode, - paintedCells, - onAddRegion, - OnRemoveRegion, -}: RegionSelectorProps) => { +const RegionSelector = ({ regions, selectedRegionIndex, mode, cameraFeedID }: RegionSelectorProps) => { + const { dispatch } = useCameraFeedContext(); const handleChange = (e: { target: { value: string } }) => { - onSelectMode(e.target.value); + dispatch({ type: "CHANGE_MODE", payload: { cameraFeedID: cameraFeedID, mode: e.target.value } }); }; - const handleAddClick = () => { - onAddRegion(); + const handleAddRegionClick = () => { + const regionName = `Region ${regions.length + 1}`; + dispatch({ + type: "ADD_NEW_REGION", + payload: { cameraFeedID: cameraFeedID, regionName: regionName, brushColour: "#ffffff" }, + }); }; - const handleResetClick = () => { - const map = paintedCells.current; - map.clear(); + const handleResetRegion = () => { + dispatch({ + type: "RESET_PAINTED_CELLS", + payload: { cameraFeedID: cameraFeedID, paintedCells: new Map() }, + }); }; 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 ( @@ -84,7 +96,7 @@ const RegionSelector = ({

Region Select

<> - {regions.map((region, idx) => { + {regions?.map((region, idx) => { const isSelected = selectedRegionIndex === idx; const inputId = `region-${idx}`; return ( @@ -102,20 +114,20 @@ const RegionSelector = ({ name="region" className="sr-only" onChange={() => { - onSelectMode("painter"); - onSelectRegion(idx); + handleModeChange("painter"); + handleRegionSelect(idx); }} /> {region.name}
- onChangeRegionColour(idx, c)} /> + handleRegionColourChange(idx, c)} />

{region.brushColour}

); })}
-
diff --git a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx index 1706412..6aa48ef 100644 --- a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx +++ b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx @@ -2,22 +2,22 @@ import { useEffect, useRef, useState, type RefObject } from "react"; import { Stage, Layer, Image, Shape } from "react-konva"; import type { KonvaEventObject } from "konva/lib/Node"; import { useCreateVideoSnapshot } from "../../hooks/useGetvideoSnapshots"; -import type { PaintedCell, Region } from "../../../../types/types"; + import Card from "../../../../ui/Card"; +import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; const rows = 40; const cols = 40; const size = 20; const gap = 0; -type VideoFeedGridPainterProps = { - regions: Region[]; - selectedRegionIndex: number; - mode: string; - paintedCells: RefObject>; -}; - -const VideoFeedGridPainter = ({ regions, selectedRegionIndex, mode, paintedCells }: VideoFeedGridPainterProps) => { +const VideoFeedGridPainter = () => { + const { state } = useCameraFeedContext(); + const cameraFeedID = state.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 [stageSize, setStageSize] = useState({ width: 740, height: 460 }); const isDrawingRef = useRef(false); @@ -47,7 +47,7 @@ const VideoFeedGridPainter = ({ regions, selectedRegionIndex, mode, paintedCells const key = `${row}-${col}`; const currentColour = regions[selectedRegionIndex].brushColour; - const map = paintedCells.current; + const map = paintedCells; const existing = map.get(key); if (mode === "eraser") { @@ -121,7 +121,7 @@ const VideoFeedGridPainter = ({ regions, selectedRegionIndex, mode, paintedCells { - const cells = paintedCells.current; + const cells = paintedCells; cells.forEach((cell, key) => { const [rowStr, colStr] = key.split("-"); const row = Number(rowStr); diff --git a/src/features/cameras/hooks/useGetVideoFeed.ts b/src/features/cameras/hooks/useGetVideoFeed.ts index bedbaeb..03b9603 100644 --- a/src/features/cameras/hooks/useGetVideoFeed.ts +++ b/src/features/cameras/hooks/useGetVideoFeed.ts @@ -1,7 +1,8 @@ import { useQuery } from "@tanstack/react-query"; +import { CAMBASE } from "../../../utils/config"; -const getfeed = async () => { - const response = await fetch(`http://100.115.148.59/TargetDetectionColour-preview`, { +const getfeed = async (cameraFeedID: "A" | "B" | "C" | null) => { + const response = await fetch(`${CAMBASE}TargetDetectionColour${cameraFeedID}-preview`, { signal: AbortSignal.timeout(300000), cache: "no-store", }); @@ -11,10 +12,10 @@ const getfeed = async () => { return response.blob(); }; -export const useGetVideoFeed = () => { +export const useGetVideoFeed = (cameraFeedID: "A" | "B" | "C" | null) => { const videoQuery = useQuery({ - queryKey: ["getfeed"], - queryFn: getfeed, + queryKey: ["getfeed", cameraFeedID], + queryFn: () => getfeed(cameraFeedID), refetchInterval: 500, }); diff --git a/src/features/cameras/hooks/useGetvideoSnapshots.ts b/src/features/cameras/hooks/useGetvideoSnapshots.ts index 4c9679c..9bf941d 100644 --- a/src/features/cameras/hooks/useGetvideoSnapshots.ts +++ b/src/features/cameras/hooks/useGetvideoSnapshots.ts @@ -1,9 +1,12 @@ import { useEffect, useRef } from "react"; import { useGetVideoFeed } from "./useGetVideoFeed"; +import { useCameraFeedContext } from "../../../app/context/CameraFeedContext"; export const useCreateVideoSnapshot = () => { + const { state } = useCameraFeedContext(); + const cameraFeedID = state?.cameraFeedID; const latestBitmapRef = useRef(null); - const { videoQuery } = useGetVideoFeed(); + const { videoQuery } = useGetVideoFeed(cameraFeedID); const snapShot = videoQuery?.data; const isloading = videoQuery.isPending; diff --git a/src/types/types.ts b/src/types/types.ts index cf5bbb7..e012fca 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -95,3 +95,52 @@ export type OptionalBOF2LaneIDs = { LID2?: string; LID3?: string; }; + +export type CameraFeedState = { + cameraFeedID: "A" | "B" | "C"; + paintedCells: { + A: Map; + B: Map; + C: Map; + }; + regionsByCamera: { + A: Region[]; + B: Region[]; + C: Region[]; + }; + selectedRegionIndex: number; + modeByCamera: { + A: string; + B: string; + C: string; + }; + + tabIndex?: number; +}; + +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 }; + };