mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 02:29:19 +03:00
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* debug replay implementation * fix masks after dev rebase * fix squash merge issues * fix * fix * fix * no need to write debug replay camera to config * camera and filter button and dropdown * add filters * add ability to edit motion and object config for debug replay * add debug draw overlay to debug replay * add guard to prevent crash when camera is no longer in camera_states * fix overflow due to radix absolutely positioned elements * increase number of messages * ensure deep_merge replaces existing list values when override is true * add back button * add debug replay to explore and review menus * clean up * clean up * update instructions to prevent exposing exception info * fix typing * refactor output logic * refactor with helper function * move init to function for consistency
196 lines
6.0 KiB
TypeScript
196 lines
6.0 KiB
TypeScript
import {
|
|
DropdownMenu,
|
|
DropdownMenuTrigger,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuPortal,
|
|
DropdownMenuSeparator,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { HiDotsHorizontal } from "react-icons/hi";
|
|
import { useApiHost } from "@/api";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Event } from "@/types/event";
|
|
import { FrigateConfig } from "@/types/frigateConfig";
|
|
import { useCallback, useState } from "react";
|
|
import { useIsAdmin } from "@/hooks/use-is-admin";
|
|
import axios from "axios";
|
|
import { toast } from "sonner";
|
|
import { Button } from "../ui/button";
|
|
|
|
type EventMenuProps = {
|
|
event: Event;
|
|
config?: FrigateConfig;
|
|
onOpenUpload?: (e: Event) => void;
|
|
onOpenSimilarity?: (e: Event) => void;
|
|
isSelected?: boolean;
|
|
onToggleSelection?: (event: Event | undefined) => void;
|
|
};
|
|
|
|
export default function EventMenu({
|
|
event,
|
|
config,
|
|
onOpenUpload,
|
|
onOpenSimilarity,
|
|
isSelected = false,
|
|
onToggleSelection,
|
|
}: EventMenuProps) {
|
|
const apiHost = useApiHost();
|
|
const navigate = useNavigate();
|
|
const { t } = useTranslation(["views/explore", "views/replay"]);
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const isAdmin = useIsAdmin();
|
|
const [isStarting, setIsStarting] = useState(false);
|
|
|
|
const handleObjectSelect = () => {
|
|
if (isSelected) {
|
|
onToggleSelection?.(undefined);
|
|
} else {
|
|
onToggleSelection?.(event);
|
|
}
|
|
};
|
|
|
|
const handleDebugReplay = useCallback(
|
|
(event: Event) => {
|
|
setIsStarting(true);
|
|
|
|
axios
|
|
.post("debug_replay/start", {
|
|
camera: event.camera,
|
|
start_time: event.start_time,
|
|
end_time: event.end_time,
|
|
})
|
|
.then((response) => {
|
|
if (response.status === 200) {
|
|
toast.success(t("dialog.toast.success", { ns: "views/replay" }), {
|
|
position: "top-center",
|
|
});
|
|
navigate("/replay");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
const errorMessage =
|
|
error.response?.data?.message ||
|
|
error.response?.data?.detail ||
|
|
"Unknown error";
|
|
|
|
if (error.response?.status === 409) {
|
|
toast.error(
|
|
t("dialog.toast.alreadyActive", { ns: "views/replay" }),
|
|
{
|
|
position: "top-center",
|
|
closeButton: true,
|
|
dismissible: false,
|
|
action: (
|
|
<a href="/replay" target="_blank" rel="noopener noreferrer">
|
|
<Button>
|
|
{t("dialog.toast.goToReplay", { ns: "views/replay" })}
|
|
</Button>
|
|
</a>
|
|
),
|
|
},
|
|
);
|
|
} else {
|
|
toast.error(t("dialog.toast.error", { error: errorMessage }), {
|
|
position: "top-center",
|
|
});
|
|
}
|
|
})
|
|
.finally(() => {
|
|
setIsStarting(false);
|
|
});
|
|
},
|
|
[navigate, t],
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<span tabIndex={0} className="sr-only" />
|
|
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
|
<DropdownMenuTrigger>
|
|
<div className="rounded p-1 pr-2" role="button">
|
|
<HiDotsHorizontal className="size-4 text-muted-foreground" />
|
|
</div>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuPortal>
|
|
<DropdownMenuContent>
|
|
<DropdownMenuItem
|
|
className="cursor-pointer"
|
|
onSelect={handleObjectSelect}
|
|
>
|
|
{isSelected
|
|
? t("itemMenu.hideObjectDetails.label")
|
|
: t("itemMenu.showObjectDetails.label")}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator className="my-0.5" />
|
|
<DropdownMenuItem
|
|
className="cursor-pointer"
|
|
onSelect={() => {
|
|
navigate(`/explore?event_id=${event.id}`);
|
|
}}
|
|
>
|
|
{t("details.item.button.viewInExplore")}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem className="cursor-pointer" asChild>
|
|
<a
|
|
download
|
|
href={
|
|
event.has_snapshot
|
|
? `${apiHost}api/events/${event.id}/snapshot.jpg`
|
|
: `${apiHost}api/events/${event.id}/thumbnail.webp`
|
|
}
|
|
>
|
|
{t("itemMenu.downloadSnapshot.label")}
|
|
</a>
|
|
</DropdownMenuItem>
|
|
|
|
{isAdmin &&
|
|
event.has_snapshot &&
|
|
event.plus_id == undefined &&
|
|
event.data.type == "object" &&
|
|
config?.plus?.enabled && (
|
|
<DropdownMenuItem
|
|
className="cursor-pointer"
|
|
onSelect={() => {
|
|
setIsOpen(false);
|
|
onOpenUpload?.(event);
|
|
}}
|
|
>
|
|
{t("itemMenu.submitToPlus.label")}
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
{event.has_snapshot && config?.semantic_search?.enabled && (
|
|
<DropdownMenuItem
|
|
className="cursor-pointer"
|
|
onSelect={() => {
|
|
if (onOpenSimilarity) onOpenSimilarity(event);
|
|
else
|
|
navigate(
|
|
`/explore?search_type=similarity&event_id=${event.id}`,
|
|
);
|
|
}}
|
|
>
|
|
{t("itemMenu.findSimilar.label")}
|
|
</DropdownMenuItem>
|
|
)}
|
|
{event.has_clip && (
|
|
<DropdownMenuItem
|
|
className="cursor-pointer"
|
|
disabled={isStarting}
|
|
onSelect={() => {
|
|
handleDebugReplay(event);
|
|
}}
|
|
>
|
|
{isStarting
|
|
? t("dialog.starting", { ns: "views/replay" })
|
|
: t("itemMenu.debugReplay.label")}
|
|
</DropdownMenuItem>
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenuPortal>
|
|
</DropdownMenu>
|
|
</>
|
|
);
|
|
}
|