2026-03-11 09:02:51 -05:00
|
|
|
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
2026-02-28 08:04:43 -06:00
|
|
|
import { Polygon } from "@/types/canvas";
|
2026-03-11 09:02:51 -05:00
|
|
|
import { subscribeWsTopic, getWsTopicValue } from "@/api/ws";
|
2026-02-28 08:04:43 -06:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hook to get enabled state for a polygon from websocket state.
|
2026-03-11 09:02:51 -05:00
|
|
|
* Subscribes to all relevant per-polygon topics so it only re-renders
|
|
|
|
|
* when one of those specific topics changes — not on every WS update.
|
2026-02-28 08:04:43 -06:00
|
|
|
*/
|
|
|
|
|
export function usePolygonStates(polygons: Polygon[]) {
|
2026-03-11 09:02:51 -05:00
|
|
|
// Build a stable sorted list of topics we need to watch
|
|
|
|
|
const topics = useMemo(() => {
|
|
|
|
|
const set = new Set<string>();
|
|
|
|
|
polygons.forEach((polygon) => {
|
|
|
|
|
const topic =
|
|
|
|
|
polygon.type === "zone"
|
|
|
|
|
? `${polygon.camera}/zone/${polygon.name}/state`
|
|
|
|
|
: polygon.type === "motion_mask"
|
|
|
|
|
? `${polygon.camera}/motion_mask/${polygon.name}/state`
|
|
|
|
|
: `${polygon.camera}/object_mask/${polygon.name}/state`;
|
|
|
|
|
set.add(topic);
|
|
|
|
|
});
|
|
|
|
|
return Array.from(set).sort();
|
|
|
|
|
}, [polygons]);
|
2026-02-28 08:04:43 -06:00
|
|
|
|
2026-03-11 09:02:51 -05:00
|
|
|
// Stable key for the topic list so subscribe/getSnapshot stay in sync
|
|
|
|
|
const topicsKey = topics.join("\0");
|
|
|
|
|
|
|
|
|
|
// Subscribe to all topics at once — re-subscribe only when the set changes
|
|
|
|
|
const subscribe = useCallback(
|
|
|
|
|
(listener: () => void) => {
|
|
|
|
|
const unsubscribes = topicsKey
|
|
|
|
|
.split("\0")
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.map((topic) => subscribeWsTopic(topic, listener));
|
|
|
|
|
return () => unsubscribes.forEach((unsub) => unsub());
|
|
|
|
|
},
|
|
|
|
|
[topicsKey],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Build a snapshot string from the current values of all topics.
|
|
|
|
|
// useSyncExternalStore uses Object.is, so we return a primitive that
|
|
|
|
|
// changes only when an observed topic's value changes.
|
|
|
|
|
const getSnapshot = useCallback(() => {
|
|
|
|
|
return topicsKey
|
|
|
|
|
.split("\0")
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.map((topic) => `${topic}=${getWsTopicValue(topic) ?? ""}`)
|
|
|
|
|
.join("\0");
|
|
|
|
|
}, [topicsKey]);
|
|
|
|
|
|
|
|
|
|
const snapshot = useSyncExternalStore(subscribe, getSnapshot);
|
|
|
|
|
|
|
|
|
|
// Parse the snapshot into a lookup map
|
2026-02-28 08:04:43 -06:00
|
|
|
return useMemo(() => {
|
2026-03-11 09:02:51 -05:00
|
|
|
// Build value map from snapshot
|
|
|
|
|
const valueMap = new Map<string, unknown>();
|
|
|
|
|
snapshot.split("\0").forEach((entry) => {
|
|
|
|
|
const eqIdx = entry.indexOf("=");
|
|
|
|
|
if (eqIdx > 0) {
|
|
|
|
|
const topic = entry.slice(0, eqIdx);
|
|
|
|
|
const val = entry.slice(eqIdx + 1) || undefined;
|
|
|
|
|
valueMap.set(topic, val);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-02-28 08:04:43 -06:00
|
|
|
|
2026-03-11 09:02:51 -05:00
|
|
|
const stateMap = new Map<string, boolean>();
|
2026-02-28 08:04:43 -06:00
|
|
|
polygons.forEach((polygon) => {
|
|
|
|
|
const topic =
|
|
|
|
|
polygon.type === "zone"
|
|
|
|
|
? `${polygon.camera}/zone/${polygon.name}/state`
|
|
|
|
|
: polygon.type === "motion_mask"
|
|
|
|
|
? `${polygon.camera}/motion_mask/${polygon.name}/state`
|
|
|
|
|
: `${polygon.camera}/object_mask/${polygon.name}/state`;
|
|
|
|
|
|
2026-03-11 09:02:51 -05:00
|
|
|
const wsValue = valueMap.get(topic);
|
2026-02-28 08:04:43 -06:00
|
|
|
const enabled =
|
|
|
|
|
wsValue === "ON"
|
|
|
|
|
? true
|
|
|
|
|
: wsValue === "OFF"
|
|
|
|
|
? false
|
|
|
|
|
: (polygon.enabled ?? true);
|
|
|
|
|
stateMap.set(
|
|
|
|
|
`${polygon.camera}/${polygon.type}/${polygon.name}`,
|
|
|
|
|
enabled,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (polygon: Polygon) => {
|
|
|
|
|
return (
|
|
|
|
|
stateMap.get(`${polygon.camera}/${polygon.type}/${polygon.name}`) ??
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
};
|
2026-03-11 09:02:51 -05:00
|
|
|
}, [polygons, snapshot]);
|
2026-02-28 08:04:43 -06:00
|
|
|
}
|