turn annotation settings into a popover

This commit is contained in:
Josh Hawkins 2025-11-05 20:49:28 -06:00
parent 02c5b9a4f5
commit 78e278d5e8
3 changed files with 59 additions and 37 deletions

View File

@ -140,7 +140,7 @@ export function AnnotationSettingsPane({
} }
return ( return (
<div className="mb-3 space-y-3 rounded-lg border border-secondary-foreground bg-background_alt p-2"> <div className="space-y-3 p-4">
<Heading as="h4" className="my-2"> <Heading as="h4" className="my-2">
{t("trackingDetails.annotationSettings.title")} {t("trackingDetails.annotationSettings.title")}
</Heading> </Heading>

View File

@ -32,6 +32,7 @@ import {
FaChevronRight, FaChevronRight,
} from "react-icons/fa"; } from "react-icons/fa";
import { TrackingDetails } from "./TrackingDetails"; import { TrackingDetails } from "./TrackingDetails";
import { AnnotationSettingsPane } from "./AnnotationSettingsPane";
import { LuSettings } from "react-icons/lu"; import { LuSettings } from "react-icons/lu";
import { DetailStreamProvider } from "@/context/detail-stream-context"; import { DetailStreamProvider } from "@/context/detail-stream-context";
import { import {
@ -74,6 +75,7 @@ import { useIsAdmin } from "@/hooks/use-is-admin";
import { getTranslatedLabel } from "@/utils/i18n"; import { getTranslatedLabel } from "@/utils/i18n";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel"; import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
import { DialogPortal } from "@radix-ui/react-dialog"; import { DialogPortal } from "@radix-ui/react-dialog";
import { useDetailStream } from "@/context/detail-stream-context";
const SEARCH_TABS = ["snapshot", "tracking_details"] as const; const SEARCH_TABS = ["snapshot", "tracking_details"] as const;
export type SearchTab = (typeof SEARCH_TABS)[number]; export type SearchTab = (typeof SEARCH_TABS)[number];
@ -147,17 +149,46 @@ function TabsWithActions({
setSearch={setSearch} setSearch={setSearch}
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
/> />
<div className="ml-2"> {pageToggle === "tracking_details" && (
<AnnotationSettingsPopover
search={search}
showControls={showControls}
setShowControls={setShowControls}
/>
)}
</div>
);
}
type AnnotationSettingsPopoverProps = {
search: SearchResult;
showControls?: boolean;
setShowControls?: (v: boolean) => void;
};
function AnnotationSettingsPopover({
search,
showControls,
setShowControls,
}: AnnotationSettingsPopoverProps) {
const { t } = useTranslation(["views/explore"]);
const { annotationOffset, setAnnotationOffset } = useDetailStream();
const [showZones, setShowZones] = useState(true);
return (
<div className="ml-2">
<Popover modal={true} open={showControls} onOpenChange={setShowControls}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <PopoverTrigger asChild>
variant={showControls ? "select" : "default"} <Button
className="size-7 p-1.5" variant={showControls ? "select" : "default"}
aria-label={t("trackingDetails.adjustAnnotationSettings")} className="size-7 p-1.5"
onClick={() => setShowControls?.(!showControls)} aria-label={t("trackingDetails.adjustAnnotationSettings")}
> >
<LuSettings className="size-5" /> <LuSettings className="size-5" />
</Button> </Button>
</PopoverTrigger>
</TooltipTrigger> </TooltipTrigger>
<TooltipPortal> <TooltipPortal>
<TooltipContent> <TooltipContent>
@ -165,7 +196,23 @@ function TabsWithActions({
</TooltipContent> </TooltipContent>
</TooltipPortal> </TooltipPortal>
</Tooltip> </Tooltip>
</div> <PopoverContent className="w-[90vw] max-w-2xl p-0" align="end">
<AnnotationSettingsPane
event={search as unknown as Event}
showZones={showZones}
setShowZones={setShowZones}
annotationOffset={annotationOffset}
setAnnotationOffset={(value) => {
if (typeof value === "function") {
const newValue = value(annotationOffset);
setAnnotationOffset(newValue);
} else {
setAnnotationOffset(value);
}
}}
/>
</PopoverContent>
</Popover>
</div> </div>
); );
} }
@ -221,8 +268,6 @@ function DialogContentComponent({
/> />
) : undefined ) : undefined
} }
showControls={showControls}
setShowControls={setShowControls}
/> />
); );
} }

View File

@ -8,8 +8,6 @@ import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { getIconForLabel } from "@/utils/iconUtil"; import { getIconForLabel } from "@/utils/iconUtil";
import { LuCircle, LuFolderX } from "react-icons/lu"; import { LuCircle, LuFolderX } from "react-icons/lu";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { AnnotationSettingsPane } from "./AnnotationSettingsPane";
import HlsVideoPlayer from "@/components/player/HlsVideoPlayer"; import HlsVideoPlayer from "@/components/player/HlsVideoPlayer";
import { baseUrl } from "@/api/baseUrl"; import { baseUrl } from "@/api/baseUrl";
import { REVIEW_PADDING } from "@/types/review"; import { REVIEW_PADDING } from "@/types/review";
@ -40,15 +38,12 @@ type TrackingDetailsProps = {
event: Event; event: Event;
fullscreen?: boolean; fullscreen?: boolean;
tabs?: React.ReactNode; tabs?: React.ReactNode;
showControls?: boolean;
setShowControls?: (v: boolean) => void;
}; };
export function TrackingDetails({ export function TrackingDetails({
className, className,
event, event,
tabs, tabs,
showControls = false,
}: TrackingDetailsProps) { }: TrackingDetailsProps) {
const videoRef = useRef<HTMLVideoElement | null>(null); const videoRef = useRef<HTMLVideoElement | null>(null);
const { t } = useTranslation(["views/explore"]); const { t } = useTranslation(["views/explore"]);
@ -58,8 +53,7 @@ export function TrackingDetails({
const [displaySource, _setDisplaySource] = useState<"video" | "image">( const [displaySource, _setDisplaySource] = useState<"video" | "image">(
"video", "video",
); );
const { setSelectedObjectIds, annotationOffset, setAnnotationOffset } = const { setSelectedObjectIds, annotationOffset } = useDetailStream();
useDetailStream();
// manualOverride holds a record-stream timestamp explicitly chosen by the // manualOverride holds a record-stream timestamp explicitly chosen by the
// user (eg, clicking a lifecycle row). When null we display `currentTime`. // user (eg, clicking a lifecycle row). When null we display `currentTime`.
@ -90,7 +84,6 @@ export function TrackingDetails({
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const [_selectedZone, setSelectedZone] = useState(""); const [_selectedZone, setSelectedZone] = useState("");
const [_lifecycleZones, setLifecycleZones] = useState<string[]>([]); const [_lifecycleZones, setLifecycleZones] = useState<string[]>([]);
const [showZones, setShowZones] = useState(true);
const [seekToTimestamp, setSeekToTimestamp] = useState<number | null>(null); const [seekToTimestamp, setSeekToTimestamp] = useState<number | null>(null);
const aspectRatio = useMemo(() => { const aspectRatio = useMemo(() => {
@ -464,22 +457,6 @@ export function TrackingDetails({
{t("trackingDetails.autoTrackingTips")} {t("trackingDetails.autoTrackingTips")}
</div> </div>
)} )}
{showControls && (
<AnnotationSettingsPane
event={event}
showZones={showZones}
setShowZones={setShowZones}
annotationOffset={annotationOffset}
setAnnotationOffset={(value) => {
if (typeof value === "function") {
const newValue = value(annotationOffset);
setAnnotationOffset(newValue);
} else {
setAnnotationOffset(value);
}
}}
/>
)}
<div className="mt-4"> <div className="mt-4">
<div <div