frigate/web/src/context/detail-stream-context.tsx
Josh Hawkins 945317b44e
Tracked Object Details pane tweaks (#20830)
* add prev/next buttons on desktop

* buttons should work with summary and grid view

* i18n

* small tweaks

* don't change dialog size

* remove heading and count

* remove icons

* spacing

* two column detail view

* add actions to dots menu

* move actions menu to its own component

* set modal to false on face library dropdown to guard against improper closures

https://github.com/shadcn-ui/ui/discussions/6908

* frigate plus layout

* remove face training

* clean up unused

* refactor to remove duplication between mobile and desktop

* turn annotation settings into a popover

* fix popover

* improve annotation offset popver

* change icon and popover text in detail stream for annotation settings

* clean up

* use drawer on mobile

* fix setter function

* use dialog ref for popover portal

* don't portal popover

* tweaks

* add button type

* lower xl max width

* fixes

* justify
2025-11-06 09:22:52 -07:00

99 lines
2.6 KiB
TypeScript

import React, { createContext, useContext, useState, useEffect } 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 ?? [],
);
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 changing cameras
useEffect(() => {
setSelectedObjectIds([]);
}, [isDetailMode, camera]);
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;
}