mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-20 20:16:42 +03:00
* Don't warn when event ids have expired for trigger sync * Import faster_whisper conditinally to avoid illegal instruction * Catch OpenVINO runtime error * fix race condition in detail stream context navigating between tracked objects in Explore would sometimes prevent the object track from appearing * Handle case where classification images are deleted * Adjust default rounded corners on larger screens * Improve flow handling for classification state * Remove images when wizard is cancelled * Improve deletion handling for classes * Set constraints on review buffers * Update to support correct data format * Set minimum duration for recording based review items * Use friendly name in review genai prompt --------- Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
145 lines
4.3 KiB
TypeScript
145 lines
4.3 KiB
TypeScript
import React, {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useEffect,
|
|
useRef,
|
|
} from "react";
|
|
import { FrigateConfig } from "@/types/frigateConfig";
|
|
import useSWR from "swr";
|
|
|
|
export interface DetailStreamContextType {
|
|
selectedObjectIds: string[];
|
|
currentTime: number;
|
|
camera: string;
|
|
annotationOffset: number; // milliseconds
|
|
setSelectedObjectIds: React.Dispatch<React.SetStateAction<string[]>>;
|
|
setAnnotationOffset: React.Dispatch<React.SetStateAction<number>>;
|
|
toggleObjectSelection: (id: string | undefined) => void;
|
|
isDetailMode: boolean;
|
|
}
|
|
|
|
const DetailStreamContext = createContext<DetailStreamContextType | undefined>(
|
|
undefined,
|
|
);
|
|
|
|
interface DetailStreamProviderProps {
|
|
children: React.ReactNode;
|
|
isDetailMode: boolean;
|
|
currentTime: number;
|
|
camera: string;
|
|
initialSelectedObjectIds?: string[];
|
|
}
|
|
|
|
export function DetailStreamProvider({
|
|
children,
|
|
isDetailMode,
|
|
currentTime,
|
|
camera,
|
|
initialSelectedObjectIds,
|
|
}: DetailStreamProviderProps) {
|
|
const [selectedObjectIds, setSelectedObjectIds] = useState<string[]>(
|
|
() => initialSelectedObjectIds ?? [],
|
|
);
|
|
|
|
// When the parent provides a new initialSelectedObjectIds (for example
|
|
// when navigating between search results) update the selection so children
|
|
// like `ObjectTrackOverlay` receive the new ids immediately. We only
|
|
// perform this update when the incoming value actually changes.
|
|
useEffect(() => {
|
|
if (
|
|
initialSelectedObjectIds &&
|
|
(initialSelectedObjectIds.length !== selectedObjectIds.length ||
|
|
initialSelectedObjectIds.some((v, i) => selectedObjectIds[i] !== v))
|
|
) {
|
|
setSelectedObjectIds(initialSelectedObjectIds);
|
|
}
|
|
// Intentionally include selectedObjectIds to compare previous value and
|
|
// avoid overwriting user interactions unless the incoming prop changed.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [initialSelectedObjectIds]);
|
|
|
|
const toggleObjectSelection = (id: string | undefined) => {
|
|
if (id === undefined) {
|
|
setSelectedObjectIds([]);
|
|
} else {
|
|
setSelectedObjectIds((prev) => {
|
|
if (prev.includes(id)) {
|
|
return prev.filter((existingId) => existingId !== id);
|
|
} else {
|
|
return [...prev, id];
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
const { data: config } = useSWR<FrigateConfig>("config");
|
|
|
|
const [annotationOffset, setAnnotationOffset] = useState<number>(() => {
|
|
if (!config) return 0;
|
|
return config.cameras[camera]?.detect?.annotation_offset || 0;
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!config) return;
|
|
const cfgOffset = config.cameras[camera]?.detect?.annotation_offset || 0;
|
|
setAnnotationOffset(cfgOffset);
|
|
}, [config, camera]);
|
|
|
|
// Clear selected objects when exiting detail mode or when the camera
|
|
// changes for providers that are not initialized with an explicit
|
|
// `initialSelectedObjectIds` (e.g., the RecordingView). For providers
|
|
// that receive `initialSelectedObjectIds` (like SearchDetailDialog) we
|
|
// avoid clearing on camera change to prevent a race with children that
|
|
// immediately set selection when mounting.
|
|
const prevCameraRef = useRef<string | undefined>(undefined);
|
|
useEffect(() => {
|
|
// Always clear when leaving detail mode
|
|
if (!isDetailMode) {
|
|
setSelectedObjectIds([]);
|
|
prevCameraRef.current = camera;
|
|
return;
|
|
}
|
|
|
|
// If camera changed and the parent did not provide initialSelectedObjectIds,
|
|
// clear selection to preserve previous behavior.
|
|
if (
|
|
prevCameraRef.current !== undefined &&
|
|
prevCameraRef.current !== camera &&
|
|
initialSelectedObjectIds === undefined
|
|
) {
|
|
setSelectedObjectIds([]);
|
|
}
|
|
|
|
prevCameraRef.current = camera;
|
|
}, [isDetailMode, camera, initialSelectedObjectIds]);
|
|
|
|
const value: DetailStreamContextType = {
|
|
selectedObjectIds,
|
|
currentTime,
|
|
camera,
|
|
annotationOffset,
|
|
setAnnotationOffset,
|
|
setSelectedObjectIds,
|
|
toggleObjectSelection,
|
|
isDetailMode,
|
|
};
|
|
|
|
return (
|
|
<DetailStreamContext.Provider value={value}>
|
|
{children}
|
|
</DetailStreamContext.Provider>
|
|
);
|
|
}
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
export function useDetailStream() {
|
|
const context = useContext(DetailStreamContext);
|
|
if (context === undefined) {
|
|
throw new Error(
|
|
"useDetailStream must be used within an DetailStreamProvider",
|
|
);
|
|
}
|
|
return context;
|
|
}
|