diff --git a/web/src/components/menu/SearchResultActions.tsx b/web/src/components/menu/SearchResultActions.tsx new file mode 100644 index 000000000..d95acc5a5 --- /dev/null +++ b/web/src/components/menu/SearchResultActions.tsx @@ -0,0 +1,218 @@ +import { useState, ReactNode } from "react"; +import { SearchResult } from "@/types/search"; +import { FrigateConfig } from "@/types/frigateConfig"; +import { baseUrl } from "@/api/baseUrl"; +import { toast } from "sonner"; +import axios from "axios"; +import { LuCamera, LuDownload, LuMoreVertical, LuTrash2 } from "react-icons/lu"; +import { FaArrowsRotate } from "react-icons/fa6"; +import { MdImageSearch } from "react-icons/md"; +import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; +import { isMobileOnly } from "react-device-detect"; +import { buttonVariants } from "@/components/ui/button"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { FrigatePlusDialog } from "@/components/overlay/dialog/FrigatePlusDialog"; +import useSWR from "swr"; +import { Event } from "@/types/event"; + +type SearchResultActionsProps = { + searchResult: SearchResult; + findSimilar: () => void; + refreshResults: () => void; + showObjectLifecycle: () => void; + isContextMenu?: boolean; + children?: ReactNode; +}; + +export default function SearchResultActions({ + searchResult, + findSimilar, + refreshResults, + showObjectLifecycle, + isContextMenu = false, + children, +}: SearchResultActionsProps) { + const { data: config } = useSWR("config"); + + const [showFrigatePlus, setShowFrigatePlus] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + + const handleDelete = () => { + axios + .delete(`events/${searchResult.id}`) + .then((resp) => { + if (resp.status == 200) { + toast.success("Tracked object deleted successfully.", { + position: "top-center", + }); + refreshResults(); + } + }) + .catch(() => { + toast.error("Failed to delete tracked object.", { + position: "top-center", + }); + }); + }; + + const MenuItem = isContextMenu ? ContextMenuItem : DropdownMenuItem; + + const menuItems = ( + <> + {searchResult.has_clip && ( + + + + Download video + + + )} + {searchResult.has_snapshot && ( + + + + Download snapshot + + + )} + + + View object lifecycle + + {config?.semantic_search?.enabled && isContextMenu && ( + + + Find similar + + )} + {isMobileOnly && + config?.plus?.enabled && + searchResult.has_snapshot && + searchResult.end_time && + !searchResult.plus_id && ( + setShowFrigatePlus(true)}> + + Submit to Frigate+ + + )} + setDeleteDialogOpen(true)}> + + Delete + + + ); + + return ( + <> + setDeleteDialogOpen(!deleteDialogOpen)} + > + + + Confirm Delete + + + Are you sure you want to delete this tracked object? + + + Cancel + + Delete + + + + + setShowFrigatePlus(false)} + onEventUploaded={() => { + searchResult.plus_id = "submitted"; + }} + /> + + {isContextMenu ? ( + + {children} + {menuItems} + + ) : ( + <> + {config?.semantic_search?.enabled && ( + + + + + Find similar + + )} + + {!isMobileOnly && + config?.plus?.enabled && + searchResult.has_snapshot && + searchResult.end_time && + !searchResult.plus_id && ( + + + setShowFrigatePlus(true)} + /> + + Submit to Frigate+ + + )} + + + + + + {menuItems} + + + )} + + ); +}