From 5900a2a4bacd5023d332b0398809cb674cddab3a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 27 May 2024 16:12:57 -0600 Subject: [PATCH] Add ability to interact with review items in events list (#11562) * Add ability to interact with review items * Ignore on iOS * Don't load metadata * Bug fixes --- web/src/components/card/ReviewCard.tsx | 152 +++++++++++++++++- web/src/components/player/HlsVideoPlayer.tsx | 4 +- web/src/components/player/LivePlayer.tsx | 2 +- web/src/components/player/PreviewPlayer.tsx | 5 +- .../player/PreviewThumbnailPlayer.tsx | 6 +- 5 files changed, 161 insertions(+), 8 deletions(-) diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx index 9950fd2d6..dacb55b69 100644 --- a/web/src/components/card/ReviewCard.tsx +++ b/web/src/components/card/ReviewCard.tsx @@ -3,12 +3,24 @@ import { useFormattedTimestamp } from "@/hooks/use-date-utils"; import { FrigateConfig } from "@/types/frigateConfig"; import { ReviewSegment } from "@/types/review"; import { getIconForLabel } from "@/utils/iconUtil"; -import { isSafari } from "react-device-detect"; +import { isDesktop, isIOS, isSafari } from "react-device-detect"; import useSWR from "swr"; import TimeAgo from "../dynamic/TimeAgo"; -import { useMemo } from "react"; +import { useCallback, useMemo, useState } from "react"; import useImageLoaded from "@/hooks/use-image-loaded"; import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator"; +import { FaCompactDisc } from "react-icons/fa"; +import { FaCircleCheck } from "react-icons/fa6"; +import { HiTrash } from "react-icons/hi"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, +} from "../ui/context-menu"; +import { Drawer, DrawerContent } from "../ui/drawer"; +import axios from "axios"; +import { toast } from "sonner"; type ReviewCardProps = { event: ReviewSegment; @@ -33,10 +45,61 @@ export default function ReviewCard({ [event, currentTime], ); - return ( + const [optionsOpen, setOptionsOpen] = useState(false); + + const onMarkAsReviewed = useCallback(async () => { + await axios.post(`reviews/viewed`, { ids: [event.id] }); + event.has_been_reviewed = true; + setOptionsOpen(false); + }, [event]); + + const onExport = useCallback(async () => { + axios + .post( + `export/${event.camera}/start/${event.start_time}/end/${event.end_time}`, + { playback: "realtime" }, + ) + .then((response) => { + if (response.status == 200) { + toast.success( + "Successfully started export. View the file in the /exports folder.", + { position: "top-center" }, + ); + } + }) + .catch((error) => { + if (error.response?.data?.message) { + toast.error( + `Failed to start export: ${error.response.data.message}`, + { position: "top-center" }, + ); + } else { + toast.error(`Failed to start export: ${error.message}`, { + position: "top-center", + }); + } + }); + setOptionsOpen(false); + }, [event]); + + const onDelete = useCallback(async () => { + await axios.post(`reviews/delete`, { ids: [event.id] }); + event.id = ""; + setOptionsOpen(false); + }, [event]); + + const content = (
{ + e.preventDefault(); + setOptionsOpen(true); + } + } > { onImgLoad(); }} @@ -69,4 +141,78 @@ export default function ReviewCard({
); + + if (event.id == "") { + return; + } + + if (isDesktop) { + return ( + + {content} + + +
+ +
Export
+
+
+ {!event.has_been_reviewed && ( + +
+ +
Mark as reviewed
+
+
+ )} + +
+ +
Delete
+
+
+
+
+ ); + } + + return ( + + {content} + +
+ +
Export
+
+ {!event.has_been_reviewed && ( +
+ +
Mark as reviewed
+
+ )} +
+ +
Delete
+
+
+
+ ); } diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 92834cd00..85d1991b1 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -272,8 +272,8 @@ export default function HlsVideoPlayer({ ? onTimeUpdate(videoRef.current.currentTime) : undefined } - onLoadedData={onPlayerLoaded} - onLoadedMetadata={() => { + onLoadedData={() => { + onPlayerLoaded?.(); handleLoadedMetadata(); if (videoRef.current) { diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 2cbd8921b..80a382a9e 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -135,7 +135,7 @@ export default function LivePlayer({ ); } } else if (liveMode == "jsmpeg") { - if (cameraActive) { + if (cameraActive || !showStillWithoutActivity) { player = ( {currentPreview != undefined && ( - + )} {cameraPreviews && !currentPreview && ( diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index a69a3bd85..1a482ae8e 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -25,6 +25,7 @@ import { TimeRange } from "@/types/timeline"; import { NoThumbSlider } from "../ui/slider"; import { PREVIEW_FPS, PREVIEW_PADDING } from "@/types/preview"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; +import { baseUrl } from "@/api/baseUrl"; type PreviewPlayerProps = { review: ReviewSegment; @@ -575,7 +576,10 @@ export function VideoPreview({ muted onTimeUpdate={onProgress} > - + {showProgress && (