From 1628048ac5a5a4e735003186c4366111b463929a Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Mon, 8 Dec 2025 10:59:46 +0000 Subject: [PATCH] - added camera black board fetch and post - region selector can save settings and painted regions and fetch on load - will add reset all --- src/app/providers/CameraFeedProvider.tsx | 26 +++++++++++++- src/app/reducers/cameraFeedReducer.ts | 8 +++++ .../CameraSettings/RegionSelector.tsx | 27 +++++++++++++- .../components/Video/VideoFeedGridPainter.tsx | 5 +-- src/hooks/useBlackBoard.ts | 35 +++++++++++++++++++ src/types/types.ts | 13 +++++++ src/ui/Card.tsx | 2 +- 7 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 src/hooks/useBlackBoard.ts diff --git a/src/app/providers/CameraFeedProvider.tsx b/src/app/providers/CameraFeedProvider.tsx index b983b8e..8e5f657 100644 --- a/src/app/providers/CameraFeedProvider.tsx +++ b/src/app/providers/CameraFeedProvider.tsx @@ -1,9 +1,33 @@ -import { useReducer, type ReactNode } from "react"; +import { useEffect, useReducer, type ReactNode } from "react"; import { CameraFeedContext } from "../context/CameraFeedContext"; import { initialState, reducer } from "../reducers/cameraFeedReducer"; +import { useBlackBoard } from "../../hooks/useBlackBoard"; +import type { CameraFeedState } from "../../types/types"; export const CameraFeedProvider = ({ children }: { children: ReactNode }) => { + const { blackboardMutation } = useBlackBoard(); const [state, dispatch] = useReducer(reducer, initialState); + useEffect(() => { + const fetchBlackBoardData = async () => { + const result = await blackboardMutation.mutateAsync({ + operation: "VIEW", + path: "cameraFeed", + }); + if (!result?.result || typeof result.result === "string") return; + const cameraFeedData: CameraFeedState = result.result; + const recontructedState = { + ...cameraFeedData, + paintedCells: { + A: new Map(cameraFeedData.paintedCells.A), + B: new Map(cameraFeedData.paintedCells.B), + C: new Map(cameraFeedData.paintedCells.C), + }, + }; + dispatch({ type: "SET_CAMERA_FEED_DATA", cameraState: recontructedState }); + }; + fetchBlackBoardData(); + }, []); + return {children}; }; diff --git a/src/app/reducers/cameraFeedReducer.ts b/src/app/reducers/cameraFeedReducer.ts index 4f2b1d8..cd8e397 100644 --- a/src/app/reducers/cameraFeedReducer.ts +++ b/src/app/reducers/cameraFeedReducer.ts @@ -98,6 +98,14 @@ export function reducer(state: CameraFeedState, action: CameraFeedAction) { [state.cameraFeedID]: new Map(), }, }; + case "SET_CAMERA_FEED_DATA": + return { + ...action.cameraState, + }; + case "RESET_CAMERA_FEED": + return { + ...initialState, + }; default: return state; diff --git a/src/features/cameras/components/CameraSettings/RegionSelector.tsx b/src/features/cameras/components/CameraSettings/RegionSelector.tsx index f7cd3af..653e39b 100644 --- a/src/features/cameras/components/CameraSettings/RegionSelector.tsx +++ b/src/features/cameras/components/CameraSettings/RegionSelector.tsx @@ -2,6 +2,8 @@ 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[]; @@ -13,6 +15,7 @@ type RegionSelectorProps = { const RegionSelector = ({ regions, selectedRegionIndex, mode, cameraFeedID }: RegionSelectorProps) => { const { colourMutation } = useColourDectection(); const { state, dispatch } = useCameraFeedContext(); + const { blackboardMutation } = useBlackBoard(); const paintedCells = state.paintedCells[cameraFeedID]; const handleChange = (e: { target: { value: string } }) => { @@ -58,6 +61,10 @@ const RegionSelector = ({ regions, selectedRegionIndex, mode, cameraFeedID }: Re }); }; + const handleResetAll = () => { + dispatch({ type: "RESET_CAMERA_FEED" }); + }; + const handleSaveclick = () => { const regions: ColourData[] = []; const test = Array.from(paintedCells.entries()); @@ -103,12 +110,24 @@ const RegionSelector = ({ regions, selectedRegionIndex, mode, cameraFeedID }: Re } 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 (
-
+

Tools

diff --git a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx index 4e20a59..1c775cc 100644 --- a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx +++ b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx @@ -16,7 +16,7 @@ const gap = 0; const VideoFeedGridPainter = () => { const { state } = useCameraFeedContext(); 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]; @@ -135,7 +135,8 @@ const VideoFeedGridPainter = () => { { const cells = paintedCells; - cells.forEach((cell, key) => { + 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); diff --git a/src/hooks/useBlackBoard.ts b/src/hooks/useBlackBoard.ts new file mode 100644 index 0000000..638fcae --- /dev/null +++ b/src/hooks/useBlackBoard.ts @@ -0,0 +1,35 @@ +import { useQuery, useMutation } from "@tanstack/react-query"; +import { CAMBASE } from "../utils/config"; +import type { BlackBoardOptions } from "../types/types"; + +const fetchBlackBoardData = async () => { + const response = await fetch(`${CAMBASE}/api/blackboard`); + if (!response.ok) { + throw new Error("Failed to fetch blackboard data"); + } + return response.json(); +}; + +const viewBlackBoardData = async (options: BlackBoardOptions) => { + const response = await fetch(`${CAMBASE}/api/blackboard`, { + method: "POST", + body: JSON.stringify(options), + }); + if (!response.ok) { + throw new Error("Failed to view blackboard data"); + } + return response.json(); +}; + +export const useBlackBoard = () => { + const blackboardQuery = useQuery({ + queryKey: ["blackboardData"], + queryFn: fetchBlackBoardData, + }); + + const blackboardMutation = useMutation({ + mutationKey: ["viewBlackBoardData"], + mutationFn: (options: BlackBoardOptions) => viewBlackBoardData(options), + }); + return { blackboardQuery, blackboardMutation }; +}; diff --git a/src/types/types.ts b/src/types/types.ts index 7d7309b..fc0befb 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -170,6 +170,13 @@ export type CameraFeedAction = | { type: "RESET_PAINTED_CELLS"; payload: { cameraFeedID: "A" | "B" | "C"; paintedCells: Map }; + } + | { + type: "SET_CAMERA_FEED_DATA"; + cameraState: CameraFeedState; + } + | { + type: "RESET_CAMERA_FEED"; }; export type DecodeReading = { @@ -214,3 +221,9 @@ export type CustomFieldConfig = { label: string; value: string; }; + +export type BlackBoardOptions = { + operation?: string; + path?: string; + value?: object | string | number | (string | number)[] | null; +}; diff --git a/src/ui/Card.tsx b/src/ui/Card.tsx index 11c0fa1..2b814a9 100644 --- a/src/ui/Card.tsx +++ b/src/ui/Card.tsx @@ -10,7 +10,7 @@ const Card = ({ children, className }: CardProps) => { return (