diff --git a/src/features/cameras/components/CameraSettings/RegionSelector.tsx b/src/features/cameras/components/CameraSettings/RegionSelector.tsx index 67f31ac..18c57b8 100644 --- a/src/features/cameras/components/CameraSettings/RegionSelector.tsx +++ b/src/features/cameras/components/CameraSettings/RegionSelector.tsx @@ -174,6 +174,27 @@ const RegionSelector = ({ /> Erase mode + diff --git a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx index b17c837..31d812e 100644 --- a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx +++ b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx @@ -23,6 +23,10 @@ const VideoFeedGridPainter = () => { const { latestBitmapRef, isloading } = useCreateVideoSnapshot(); const [stageSize, setStageSize] = useState({ width: BACKEND_WIDTH, height: BACKEND_HEIGHT }); 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); const currentScale = stageSize.width / BACKEND_WIDTH; const size = BACKEND_CELL_SIZE * currentScale; @@ -71,14 +75,14 @@ const VideoFeedGridPainter = () => { }; const handleStageMouseDown = (e: KonvaEventObject) => { - if (!regions[selectedRegionIndex]) return; + if (!regions[selectedRegionIndex] || 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) return; + if (!isDrawingRef.current || mode === "zoom") return; if (!regions[selectedRegionIndex]) return; const pos = e.target.getStage()?.getPointerPosition(); if (pos) paintCell(pos.x, pos.y); @@ -88,6 +92,38 @@ const VideoFeedGridPainter = () => { isDrawingRef.current = false; }; + const handleMouseEnter = () => { + if (mode !== "zoom") return; + setScale(2); + }; + const handleMouseLeave = () => { + document.body.style.cursor = "default"; + setScale(1); + setPosition({ x: 0, y: 0 }); + }; + const handleMouseMove = (e: KonvaEventObject) => { + if (scale === 1) return; + + const stage = e.target.getStage(); + if (!stage) return; + + const pointerPosition = stage.getPointerPosition(); + if (!pointerPosition) return; + + const newX = stageSize.width / 2 - pointerPosition.x * scale; + const newY = stageSize.height / 2 - pointerPosition.y * scale; + + const maxX = 0; + const minX = stageSize.width - stageSize.width * scale; + const maxY = 0; + const minY = stageSize.height - stageSize.height * scale; + + setPosition({ + x: Math.max(minX, Math.min(maxX, newX)), + y: Math.max(minY, Math.min(maxY, newY)), + }); + }; + useEffect(() => { const handleResize = () => { const width = window.innerWidth; @@ -112,48 +148,57 @@ const VideoFeedGridPainter = () => { if (image === null || isloading) return Loading Video feed…; return ( -
+
- + - { - 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); + {mode === "painter" || mode === "eraser" ? ( + { + 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 * (size + gap); - const y = row * (size + gap); + const x = col * (size + gap); + const y = row * (size + gap); - ctx.beginPath(); - ctx.rect(x, y, size, size); - ctx.fillStyle = cell.colour; - ctx.fill(); - }); + ctx.beginPath(); + ctx.rect(x, y, size, size); + ctx.fillStyle = cell.colour; + ctx.fill(); + }); - ctx.fillStrokeShape(shape); - }} - width={stageSize.width} - height={stageSize.height} - /> + ctx.fillStrokeShape(shape); + }} + width={stageSize.width} + height={stageSize.height} + /> + ) : null}
diff --git a/src/features/cameras/hooks/useGetVideoFeed.ts b/src/features/cameras/hooks/useGetVideoFeed.ts index 660458a..44b2796 100644 --- a/src/features/cameras/hooks/useGetVideoFeed.ts +++ b/src/features/cameras/hooks/useGetVideoFeed.ts @@ -1,7 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { CAMBASE } from "../../../utils/config"; -const getfeed = async (cameraFeedID: "A" | "B" | "C" | null) => { +const targetDectionFeed = async (cameraFeedID: "A" | "B" | "C" | null) => { const response = await fetch(`${CAMBASE}/TargetDetectionColour${cameraFeedID}-preview`, { signal: AbortSignal.timeout(300000), cache: "no-store", @@ -12,12 +12,31 @@ const getfeed = async (cameraFeedID: "A" | "B" | "C" | null) => { return response.blob(); }; -export const useGetVideoFeed = (cameraFeedID: "A" | "B" | "C" | null) => { - const videoQuery = useQuery({ +const getVideoFeed = async (cameraFeedID: "A" | "B" | "C" | null) => { + const response = await fetch(`${CAMBASE}/Camera${cameraFeedID}-preview`, { + signal: AbortSignal.timeout(300000), + cache: "no-store", + }); + if (!response.ok) { + throw new Error(`Cannot reach endpoint (${response.status})`); + } + return response.blob(); +}; + +export const useGetVideoFeed = (cameraFeedID: "A" | "B" | "C" | null, mode: string) => { + const targetDetectionQuery = useQuery({ queryKey: ["getfeed", cameraFeedID], - queryFn: () => getfeed(cameraFeedID), + queryFn: () => targetDectionFeed(cameraFeedID), refetchInterval: 500, + enabled: mode !== "zoom", }); - return { videoQuery }; + const videoFeedQuery = useQuery({ + queryKey: ["videoQuery", cameraFeedID, mode], + queryFn: () => getVideoFeed(cameraFeedID), + refetchInterval: 500, + enabled: mode === "zoom", + }); + + return { targetDetectionQuery, videoFeedQuery }; }; diff --git a/src/features/cameras/hooks/useGetvideoSnapshots.ts b/src/features/cameras/hooks/useGetvideoSnapshots.ts index 9bf941d..da70292 100644 --- a/src/features/cameras/hooks/useGetvideoSnapshots.ts +++ b/src/features/cameras/hooks/useGetvideoSnapshots.ts @@ -5,11 +5,19 @@ import { useCameraFeedContext } from "../../../app/context/CameraFeedContext"; export const useCreateVideoSnapshot = () => { const { state } = useCameraFeedContext(); const cameraFeedID = state?.cameraFeedID; + const mode = state.modeByCamera[cameraFeedID]; const latestBitmapRef = useRef(null); - const { videoQuery } = useGetVideoFeed(cameraFeedID); + const { targetDetectionQuery, videoFeedQuery } = useGetVideoFeed(cameraFeedID, mode); - const snapShot = videoQuery?.data; - const isloading = videoQuery.isPending; + let snapShot = targetDetectionQuery?.data; + const isloading = targetDetectionQuery.isPending; + + const videoSnapShot = videoFeedQuery?.data; + const isVideoLoading = videoFeedQuery.isPending; + + if (isVideoLoading === false && videoSnapShot && mode === "zoom") { + snapShot = videoSnapShot; + } useEffect(() => { async function createBitmap() {