Refactor camera feed handling to support dynamic camera IDs and improve context management

This commit is contained in:
2025-12-17 14:19:23 +00:00
parent cc8b3a5691
commit 775fce7900
19 changed files with 211 additions and 248 deletions

View File

@@ -28,6 +28,7 @@ const CameraPanel = ({ tabIndex, isResetAllModalOpen, handleClose, setIsResetMod
return "B";
case 2:
return "C";
//Add more cases if more cameras are added
default:
return "A";
}

View File

@@ -2,6 +2,7 @@ import Card from "../../../../ui/Card";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import "react-tabs/style/react-tabs.css";
import CameraPanel from "./CameraPanel";
import { CAMERA_IDS } from "../../../../app/config/cameraConfig";
type CameraSettingsProps = {
setTabIndex: (tabIndex: number) => void;
@@ -12,7 +13,6 @@ type CameraSettingsProps = {
};
const CameraSettings = ({
tabIndex,
setTabIndex,
isResetAllModalOpen,
handleClose,
@@ -26,34 +26,20 @@ const CameraSettings = ({
onSelect={(index) => setTabIndex(index)}
>
<TabList>
<Tab>Camera A</Tab>
<Tab>Camera B</Tab>
<Tab>Camera C</Tab>
{CAMERA_IDS.map((id) => (
<Tab key={id}>Camera {id}</Tab>
))}
</TabList>
<TabPanel>
<CameraPanel
tabIndex={tabIndex}
isResetAllModalOpen={isResetAllModalOpen}
handleClose={handleClose}
setIsResetModalOpen={setIsResetModalOpen}
/>
</TabPanel>
<TabPanel>
<CameraPanel
tabIndex={tabIndex}
isResetAllModalOpen={isResetAllModalOpen}
handleClose={handleClose}
setIsResetModalOpen={setIsResetModalOpen}
/>
</TabPanel>
<TabPanel>
<CameraPanel
tabIndex={tabIndex}
isResetAllModalOpen={isResetAllModalOpen}
handleClose={handleClose}
setIsResetModalOpen={setIsResetModalOpen}
/>
</TabPanel>
{CAMERA_IDS.map((id, index) => (
<TabPanel key={id}>
<CameraPanel
tabIndex={index}
isResetAllModalOpen={isResetAllModalOpen}
handleClose={handleClose}
setIsResetModalOpen={setIsResetModalOpen}
/>
</TabPanel>
))}
</Tabs>
</Card>
);

View File

@@ -4,17 +4,14 @@ import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"
import { useColourDectection } from "../../hooks/useColourDetection";
import { useBlackBoard } from "../../../../hooks/useBlackBoard";
import { toast } from "sonner";
import {
useCameraFeedASocket,
useCameraFeedBSocket,
useCameraFeedCSocket,
} from "../../../../app/context/WebSocketContext";
import { useCameraFeedSocket } from "../../../../app/context/WebSocketContext";
import type { CameraID } from "../../../../app/config/cameraConfig";
type RegionSelectorProps = {
regions: Region[];
selectedRegionIndex: number;
mode: string;
cameraFeedID: "A" | "B" | "C";
cameraFeedID: CameraID;
isResetAllModalOpen: boolean;
handleClose: () => void;
setIsResetModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
@@ -33,26 +30,24 @@ const RegionSelector = ({
const { state, dispatch } = useCameraFeedContext();
const { blackboardMutation } = useBlackBoard();
const paintedCells = state.paintedCells[cameraFeedID];
const cameraASocket = useCameraFeedASocket();
const cameraBSocket = useCameraFeedBSocket();
const cameraCSocket = useCameraFeedCSocket();
const cameraSocket = useCameraFeedSocket();
const getCurrentSocket = () => {
switch (cameraFeedID) {
case "A":
return cameraASocket;
return cameraSocket;
case "B":
return cameraBSocket;
return cameraSocket;
case "C":
return cameraCSocket;
return cameraSocket;
}
};
const socket = getCurrentSocket();
const getMagnificationLevel = () => {
const test = socket.data;
if (!socket.data) return null;
const test = socket?.data;
if (!socket?.data) return null;
console.log(test);
if (!test || !test.magnificationLevel) return "1x";
return test?.magnificationLevel;

View File

@@ -1,10 +1,11 @@
import type { CameraID } from "../../../../../app/config/cameraConfig";
import { useCameraFeedContext } from "../../../../../app/context/CameraFeedContext";
import SliderComponent from "../../../../../ui/SliderComponent";
import { useCameraZoom } from "../../../hooks/useCameraZoom";
import { useDebouncedCallback } from "use-debounce";
type CameraControlsProps = {
cameraFeedID: "A" | "B" | "C";
cameraFeedID: CameraID;
};
const CameraControls = ({ cameraFeedID }: CameraControlsProps) => {

View File

@@ -3,13 +3,10 @@ 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 { useCameraFeedSocket } from "../../../../app/context/WebSocketContext";
import { ReadyState } from "react-use-websocket";
import { toast } from "sonner";
import type { CameraID } from "../../../../app/config/cameraConfig";
const BACKEND_WIDTH = 640;
const BACKEND_HEIGHT = 360;
@@ -41,22 +38,20 @@ const VideoFeedGridPainter = () => {
const currentScale = stageSize.width / BACKEND_WIDTH;
const size = BACKEND_CELL_SIZE * currentScale;
const cameraASocket = useCameraFeedASocket();
const cameraBSocket = useCameraFeedBSocket();
const cameraCSocket = useCameraFeedCSocket();
const cameraSocket = useCameraFeedSocket();
const getCurrentSocket = () => {
switch (cameraFeedID) {
case "A":
return cameraASocket;
return cameraSocket;
case "B":
return cameraBSocket;
return cameraSocket;
case "C":
return cameraCSocket;
return cameraSocket;
}
};
const handleZoomClick = (e: KonvaEventObject<MouseEvent>, cameraFeedID: "A" | "B" | "C") => {
const handleZoomClick = (e: KonvaEventObject<MouseEvent>, cameraFeedID: CameraID) => {
if (mode !== "zoom") return;
const socket = getCurrentSocket();
const stage = e.target.getStage();

View File

@@ -1,6 +1,7 @@
import { useQuery, useMutation } from "@tanstack/react-query";
import { CAMBASE } from "../../../utils/config";
import type { CameraZoomConfig } from "../../../types/types";
import type { CameraID } from "../../../app/config/cameraConfig";
const fetchZoomLevel = async (cameraFeedID: string) => {
const response = await fetch(`${CAMBASE}/api/fetch-config?id=Camera${cameraFeedID}-onvif-controller`);
@@ -34,7 +35,7 @@ const postZoomLevel = async (zoomConfig: CameraZoomConfig) => {
return response.json();
};
export const useCameraZoom = (cameraFeedID: "A" | "B" | "C") => {
export const useCameraZoom = (cameraFeedID: CameraID) => {
const cameraZoomQuery = useQuery({
queryKey: ["cameraZoom", cameraFeedID],
queryFn: () => fetchZoomLevel(cameraFeedID),

View File

@@ -3,6 +3,7 @@ import { useGetSystemHealth } from "../hooks/useGetSystemHealth";
import CameraStatus from "./cameraStatus/CameraStatus";
import SystemHealthCard from "./systemHealth/SystemHealthCard";
import SystemStatusCard from "./systemStatus/SystemStatusCard";
import { CAMERA_IDS } from "../../../app/config/cameraConfig";
const DashboardGrid = () => {
const { query } = useGetSystemHealth();
@@ -26,30 +27,35 @@ const DashboardGrid = () => {
channelA: [],
channelB: [],
channelC: [],
// todo: check if more cameras will be added later
default: [],
},
);
const categoryA = statusCategories?.channelA ?? [];
const categoryB = statusCategories?.channelB ?? [];
const categoryC = statusCategories?.channelC ?? [];
return (
<div className="grid grid-cols-1 md:grid-rows-2 md:grid-cols-2 gap-4">
<SystemStatusCard />
<SystemHealthCard
startTime={startTime}
uptime={uptime}
statuses={statuses}
isLoading={isLoading}
isError={isError}
dateUpdatedAt={dateUpdatedAt}
refetch={refetch}
/>
<div className="grid grid-cols-1 md:col-span-2 md:grid-cols-3 gap-x-4">
<CameraStatus title="Camera A" category={categoryA} isError={isError} />
<CameraStatus title="Camera B" category={categoryB} isError={isError} />
<CameraStatus title="Camera C" category={categoryC} isError={isError} />
<div className="grid grid-cols-1 md:grid-rows-0 md:grid-cols-2 gap-4 md:col-span-2">
<SystemStatusCard />
<SystemHealthCard
startTime={startTime}
uptime={uptime}
statuses={statuses}
isLoading={isLoading}
isError={isError}
dateUpdatedAt={dateUpdatedAt}
refetch={refetch}
/>
</div>
<div className="grid grid-cols-1 md:col-span-2 md:grid-cols-[repeat(3,1fr)] gap-x-4">
{CAMERA_IDS.map((cameraID) => (
<CameraStatus
key={cameraID}
title={`Camera ${cameraID}`}
category={statusCategories?.[`channel${cameraID}`] ?? []}
isError={isError}
/>
))}
</div>
</div>
);

View File

@@ -25,7 +25,7 @@ const SystemHealthCard = ({
refetch,
}: SystemOverviewProps) => {
return (
<Card className="p-4">
<Card className="p-4 ">
<CardHeader title="System Health" refetch={refetch} icon={faArrowsRotate} />
<SystemHealth
startTime={startTime}

View File

@@ -29,7 +29,7 @@ const SystemStatusCard = () => {
);
}
return (
<Card className="p-4">
<Card className="p-4 ">
<CardHeader title="System Status" />
{stats ? (
<div className="grid grid-cols-2 grid-rows-2 gap-4 col-span-2">