diff --git a/src/app/context/WebSocketContext.ts b/src/app/context/WebSocketContext.ts index dd89d90..551e006 100644 --- a/src/app/context/WebSocketContext.ts +++ b/src/app/context/WebSocketContext.ts @@ -1,6 +1,6 @@ import { createContext, useContext } from "react"; import { ReadyState } from "react-use-websocket"; -import type { InfoBarData } from "../../types/types"; +import type { CameraZoomData, InfoBarData } from "../../types/types"; type InfoSocketState = { data: InfoBarData | null; @@ -10,7 +10,7 @@ type InfoSocketState = { }; type CameraSocketState = { - data: null; + data: CameraZoomData | null; readyState: ReadyState; send: (msg: string) => void; }; diff --git a/src/app/providers/WebSocketProvider.tsx b/src/app/providers/WebSocketProvider.tsx index d2346a2..66ce180 100644 --- a/src/app/providers/WebSocketProvider.tsx +++ b/src/app/providers/WebSocketProvider.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState, type ReactNode } from "react"; import { WebsocketContext, type WebSocketConextValue } from "../context/WebSocketContext"; import useWebSocket from "react-use-websocket"; import { wsConfig } from "../config/wsconfig"; -import type { InfoBarData } from "../../types/types"; +import type { CameraZoomData, InfoBarData } from "../../types/types"; type WebSocketProviderProps = { children: ReactNode; @@ -10,7 +10,7 @@ type WebSocketProviderProps = { export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { const [systemData, setSystemData] = useState(null); - + const [socketData, setSocketData] = useState(null); const infoSocket = useWebSocket(wsConfig.infoBar, { share: true, shouldReconnect: () => true }); const cameraFeedASocket = useWebSocket(wsConfig.cameraFeedA, { share: true, shouldReconnect: () => true }); const cameraFeedBSocket = useWebSocket(wsConfig.cameraFeedB, { share: true, shouldReconnect: () => true }); @@ -23,9 +23,20 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { const data = JSON.parse(text); setSystemData(data); } + if (cameraFeedASocket.lastMessage || cameraFeedBSocket.lastMessage || cameraFeedCSocket.lastMessage) { + const message = cameraFeedASocket.lastMessage || cameraFeedBSocket.lastMessage || cameraFeedCSocket.lastMessage; + const data = await message?.data.text(); + const parsedData: CameraZoomData = JSON.parse(data || ""); + setSocketData(parsedData); + } } parseData(); - }, [infoSocket.lastMessage]); + }, [ + cameraFeedASocket.lastMessage, + cameraFeedBSocket.lastMessage, + cameraFeedCSocket.lastMessage, + infoSocket.lastMessage, + ]); const value = useMemo( () => ({ @@ -35,19 +46,19 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { sendJson: infoSocket.sendJsonMessage, }, cameraFeedA: { - data: null, + data: socketData, readyState: cameraFeedASocket.readyState, send: cameraFeedASocket.sendMessage, }, cameraFeedB: { - data: null, + data: socketData, readyState: cameraFeedBSocket.readyState, send: cameraFeedBSocket.sendMessage, }, cameraFeedC: { - data: null, + data: socketData, readyState: cameraFeedCSocket.readyState, send: cameraFeedCSocket.sendMessage, @@ -62,6 +73,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { cameraFeedCSocket.sendMessage, infoSocket.readyState, infoSocket.sendJsonMessage, + socketData, systemData, ], ); diff --git a/src/features/cameras/components/CameraSettings/RegionSelector.tsx b/src/features/cameras/components/CameraSettings/RegionSelector.tsx index 4b78604..a989bd9 100644 --- a/src/features/cameras/components/CameraSettings/RegionSelector.tsx +++ b/src/features/cameras/components/CameraSettings/RegionSelector.tsx @@ -4,7 +4,6 @@ import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext" import { useColourDectection } from "../../hooks/useColourDetection"; import { useBlackBoard } from "../../../../hooks/useBlackBoard"; import { toast } from "sonner"; -import { ReadyState } from "react-use-websocket"; import { useCameraFeedASocket, useCameraFeedBSocket, @@ -34,8 +33,6 @@ const RegionSelector = ({ const { state, dispatch } = useCameraFeedContext(); const { blackboardMutation } = useBlackBoard(); const paintedCells = state.paintedCells[cameraFeedID]; - - // Get the socket for the current camera only const cameraASocket = useCameraFeedASocket(); const cameraBSocket = useCameraFeedBSocket(); const cameraCSocket = useCameraFeedCSocket(); @@ -51,6 +48,16 @@ const RegionSelector = ({ } }; + const socket = getCurrentSocket(); + + const getMagnificationLevel = () => { + const test = socket.data; + if (!socket.data) return null; + + if (!test || !test.magnificationLevel) return "0x"; + return test?.magnificationLevel; + }; + const handleChange = (e: { target: { value: string } }) => { dispatch({ type: "CHANGE_MODE", payload: { cameraFeedID: cameraFeedID, mode: e.target.value } }); }; @@ -99,23 +106,6 @@ const RegionSelector = ({ setIsResetModalOpen(true); }; - const textClick = (cameraFeedID: "A" | "B" | "C") => { - const socket = getCurrentSocket(); - - // Check if WebSocket is connected - if (socket.readyState !== ReadyState.OPEN) { - toast.error(`Camera ${cameraFeedID} WebSocket is not connected`); - return; - } - - try { - socket.send("ZOOM=0.3,0.3"); - toast.success(`Zoom command sent to Camera ${cameraFeedID}`); - } catch (error) { - console.error("WebSocket send error:", error); - toast.error(`Failed to send command to Camera ${cameraFeedID}`); - } - }; const handleSaveclick = () => { const regions: ColourData[] = []; const test = Array.from(paintedCells.entries()); @@ -213,6 +203,25 @@ const RegionSelector = ({ /> Erase mode + - diff --git a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx index 8d0b264..02343b9 100644 --- a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx +++ b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx @@ -2,8 +2,14 @@ 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 { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; +import { + useCameraFeedASocket, + useCameraFeedBSocket, + useCameraFeedCSocket, +} from "../../../../app/context/WebSocketContext"; +import { ReadyState } from "react-use-websocket"; +import { toast } from "sonner"; const BACKEND_WIDTH = 640; const BACKEND_HEIGHT = 360; @@ -25,6 +31,7 @@ const VideoFeedGridPainter = () => { const isDrawingRef = useRef(false); const [scale, setScale] = useState(1); const [position, setPosition] = useState({ x: 0, y: 0 }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const stageRef = useRef(null); @@ -34,6 +41,55 @@ const VideoFeedGridPainter = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const paintLayerRef = useRef(null); + const cameraASocket = useCameraFeedASocket(); + const cameraBSocket = useCameraFeedBSocket(); + const cameraCSocket = useCameraFeedCSocket(); + + const getCurrentSocket = () => { + switch (cameraFeedID) { + case "A": + return cameraASocket; + case "B": + return cameraBSocket; + case "C": + return cameraCSocket; + } + }; + + const socket = getCurrentSocket(); + + const getMagnificationLevel = () => { + const test = socket.data; + if (!socket.data) return null; + + if (!test || !test.magnificationLevel) return "0x"; + return test?.magnificationLevel; + }; + + const handleZoomClick = (e: KonvaEventObject, cameraFeedID: "A" | "B" | "C") => { + if (mode !== "zoom") return; + const socket = getCurrentSocket(); + const stage = e.target.getStage(); + const coords = stage?.getPointerPosition(); + if (!coords || !socket) return; + + const newX = coords.x / stageSize.width; + const newY = coords.y / stageSize.height; + + // Check if WebSocket is connected + if (socket.readyState !== ReadyState.OPEN) { + toast.error(`Camera ${cameraFeedID} WebSocket is not connected`); + return; + } + + try { + socket.send(`ZOOM=${newX.toFixed(2)},${newY.toFixed(2)}`); + } catch (error) { + console.error("WebSocket send error:", error); + toast.error(`Failed to send command to Camera ${cameraFeedID}`); + } + }; + const draw = (bmp: RefObject): ImageBitmap | null => { if (!bmp || !bmp.current) { return null; @@ -76,14 +132,14 @@ const VideoFeedGridPainter = () => { }; const handleStageMouseDown = (e: KonvaEventObject) => { - if (!regions[selectedRegionIndex] || mode === "zoom") return; + if (!regions[selectedRegionIndex] || mode === "magnify" || mode === "zoom") return; isDrawingRef.current = true; const pos = e.target.getStage()?.getPointerPosition(); if (pos) paintCell(pos.x, pos.y); }; const handleStageMouseMove = (e: KonvaEventObject) => { - if (!isDrawingRef.current || mode === "zoom") return; + if (!isDrawingRef.current || mode === "magnify") return; if (!regions[selectedRegionIndex]) return; const pos = e.target.getStage()?.getPointerPosition(); if (pos) paintCell(pos.x, pos.y); @@ -94,7 +150,7 @@ const VideoFeedGridPainter = () => { }; const handleMouseEnter = () => { - if (mode !== "zoom") return; + if (mode !== "magnify") return; setScale(2); }; const handleMouseLeave = () => { @@ -171,7 +227,13 @@ const VideoFeedGridPainter = () => { x={position.x} y={position.y} > - + handleZoomClick(e, cameraFeedID)} + /> diff --git a/src/features/cameras/hooks/useGetVideoFeed.ts b/src/features/cameras/hooks/useGetVideoFeed.ts index 44b2796..cbee4ac 100644 --- a/src/features/cameras/hooks/useGetVideoFeed.ts +++ b/src/features/cameras/hooks/useGetVideoFeed.ts @@ -28,14 +28,14 @@ export const useGetVideoFeed = (cameraFeedID: "A" | "B" | "C" | null, mode: stri queryKey: ["getfeed", cameraFeedID], queryFn: () => targetDectionFeed(cameraFeedID), refetchInterval: 500, - enabled: mode !== "zoom", + enabled: mode !== "magnify" && mode !== "zoom", }); const videoFeedQuery = useQuery({ queryKey: ["videoQuery", cameraFeedID, mode], queryFn: () => getVideoFeed(cameraFeedID), refetchInterval: 500, - enabled: mode === "zoom", + enabled: mode === "magnify" || mode === "zoom", }); return { targetDetectionQuery, videoFeedQuery }; diff --git a/src/features/cameras/hooks/useGetvideoSnapshots.ts b/src/features/cameras/hooks/useGetvideoSnapshots.ts index da70292..f503584 100644 --- a/src/features/cameras/hooks/useGetvideoSnapshots.ts +++ b/src/features/cameras/hooks/useGetvideoSnapshots.ts @@ -15,7 +15,7 @@ export const useCreateVideoSnapshot = () => { const videoSnapShot = videoFeedQuery?.data; const isVideoLoading = videoFeedQuery.isPending; - if (isVideoLoading === false && videoSnapShot && mode === "zoom") { + if ((isVideoLoading === false && videoSnapShot && mode === "magnify") || mode === "zoom") { snapShot = videoSnapShot; } diff --git a/src/types/types.ts b/src/types/types.ts index a6c1138..0ecad3d 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -11,6 +11,10 @@ export type InfoBarData = { "thread-count": string; }; +export type CameraZoomData = { + magnificationLevel: string; +}; + export type StatusIndicator = "neutral-quaternary" | "dark" | "info" | "success" | "warning" | "danger"; export type Region = { name: string;