From ebf34ce378a5f66d73a1a58317b08110ccec1e31 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 1 Mar 2024 10:56:45 -0700 Subject: [PATCH] Revamp multiselect (#10172) * Just use multiselect for mobile * Update revamped design and add export to values * Get actions looking good on mobile and desktop --- .../components/filter/ReviewActionGroup.tsx | 83 ++++--- .../player/PreviewThumbnailPlayer.tsx | 224 ++++++------------ web/src/views/events/EventView.tsx | 41 ++-- 3 files changed, 133 insertions(+), 215 deletions(-) diff --git a/web/src/components/filter/ReviewActionGroup.tsx b/web/src/components/filter/ReviewActionGroup.tsx index 15eddfb28..1453792f8 100644 --- a/web/src/components/filter/ReviewActionGroup.tsx +++ b/web/src/components/filter/ReviewActionGroup.tsx @@ -1,21 +1,19 @@ -import { LuCheckSquare, LuTrash, LuX } from "react-icons/lu"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "../ui/tooltip"; +import { LuCheckSquare, LuFileUp, LuTrash } from "react-icons/lu"; import { useCallback } from "react"; import axios from "axios"; +import { Button } from "../ui/button"; +import { isDesktop } from "react-device-detect"; type ReviewActionGroupProps = { selectedReviews: string[]; setSelectedReviews: (ids: string[]) => void; + onExport: (id: string) => void; pullLatestData: () => void; }; export default function ReviewActionGroup({ selectedReviews, setSelectedReviews, + onExport, pullLatestData, }: ReviewActionGroupProps) { const onClearSelected = useCallback(() => { @@ -37,36 +35,47 @@ export default function ReviewActionGroup({ }, [selectedReviews, setSelectedReviews, pullLatestData]); return ( -
- - - -
- -
-
- Unselect All -
-
- - -
- -
-
- Mark Selected As Reviewed -
-
|
- - -
- -
-
- Delete Selected -
-
-
+
+
+
{`${selectedReviews.length} selected | `}
+ +
+
+ {selectedReviews.length == 1 && ( + + )} + + +
); } diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index d9e6b29dc..5a8dacedd 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -15,16 +15,6 @@ import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { isFirefox, isMobile, isSafari } from "react-device-detect"; import Chip from "../Chip"; -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuSeparator, - ContextMenuTrigger, -} from "../ui/context-menu"; -import { LuCheckCheck, LuCheckSquare, LuFileUp, LuTrash } from "react-icons/lu"; -import { RiCheckboxMultipleLine } from "react-icons/ri"; -import axios from "axios"; import { useFormattedTimestamp } from "@/hooks/use-date-utils"; import useImageLoaded from "@/hooks/use-image-loaded"; import { Skeleton } from "../ui/skeleton"; @@ -35,7 +25,6 @@ type PreviewPlayerProps = { allPreviews?: Preview[]; onTimeUpdate?: React.Dispatch>; setReviewed: (reviewId: string) => void; - markAboveReviewed: () => void; onClick: (reviewId: string, ctrl: boolean) => void; }; @@ -51,7 +40,6 @@ export default function PreviewThumbnailPlayer({ review, allPreviews, setReviewed, - markAboveReviewed, onClick, onTimeUpdate, }: PreviewPlayerProps) { @@ -167,85 +155,76 @@ export default function PreviewThumbnailPlayer({ ); return ( - - -
onPlayback(true)} - onMouseLeave={isMobile ? undefined : () => onPlayback(false)} - onClick={handleOnClick} - {...swipeHandlers} - > - {playingBack && ( -
- -
- )} - -
- { - onImgLoad(); - }} - /> - - {!playingBack && ( - <> -
-
- {(review.severity == "alert" || - review.severity == "detection") && ( - - {review.data.objects.map((object) => { - return getIconForLabel(object, "size-3 text-white"); - })} - {review.data.audio.map((audio) => { - return getIconForLabel(audio, "size-3 text-white"); - })} - {review.data.sub_labels?.map((sub) => { - return getIconForSubLabel(sub, "size-3 text-white"); - })} - - )} -
-
-
-
- - {formattedDate} -
-
- - )} -
- {!playingBack && imgLoaded && review.has_been_reviewed && ( -
- )} +
onPlayback(true)} + onMouseLeave={isMobile ? undefined : () => onPlayback(false)} + onContextMenu={(e) => { + e.preventDefault(); + onClick(review.id, true); + }} + onClick={handleOnClick} + {...swipeHandlers} + > + {playingBack && ( +
+
- - onClick(review.id, true)} - setReviewed={handleSetReviewed} - markAboveReviewed={markAboveReviewed} - /> - + )} + +
+ { + onImgLoad(); + }} + /> + + {!playingBack && ( + <> +
+
+ {(review.severity == "alert" || + review.severity == "detection") && ( + + {review.data.objects.map((object) => { + return getIconForLabel(object, "size-3 text-white"); + })} + {review.data.audio.map((audio) => { + return getIconForLabel(audio, "size-3 text-white"); + })} + {review.data.sub_labels?.map((sub) => { + return getIconForSubLabel(sub, "size-3 text-white"); + })} + + )} +
+
+
+
+ + {formattedDate} +
+
+ + )} +
+ {!playingBack && imgLoaded && review.has_been_reviewed && ( +
+ )} +
); } @@ -613,71 +592,6 @@ function InProgressPreview({ ); } -type PreviewContextItemsProps = { - review: ReviewSegment; - onSelect: () => void; - setReviewed: () => void; - markAboveReviewed: () => void; -}; -function PreviewContextItems({ - review, - onSelect, - setReviewed, - markAboveReviewed, -}: PreviewContextItemsProps) { - const exportReview = useCallback(() => { - axios.post( - `export/${review.camera}/start/${review.start_time}/end/${review.end_time}`, - { playback: "realtime" }, - ); - }, [review]); - - const deleteReview = useCallback(() => { - axios.delete(`reviews/${review.id}`); - }, [review]); - - return ( - - {isMobile && ( - -
- Select - -
-
- )} - -
- Mark Above as Reviewed - -
-
- - {!review.has_been_reviewed && ( - (setReviewed ? setReviewed() : null)}> -
- Mark As Reviewed - -
-
- )} - -
- Export - -
-
- - -
- Delete - -
-
-
- ); -} - function PreviewPlaceholder({ imgLoaded }: { imgLoaded: boolean }) { if (imgLoaded) { return; diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index d194e156e..9889a6d6c 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -223,28 +223,21 @@ export default function EventView({ [selectedReviews, setSelectedReviews], ); - const markScrolledItemsAsReviewed = useCallback(async () => { - if (!currentItems) { - return; - } + const exportReview = useCallback( + (id: string) => { + const review = currentItems?.find((seg) => seg.id == id); - const scrolled: string[] = []; - - currentItems.find((value) => { - if (value.start_time > minimapBounds.end) { - scrolled.push(value.id); - return false; - } else { - return true; + if (!review) { + return; } - }); - const idList = scrolled.join(","); - - await axios.post(`reviews/${idList}/viewed`); - setSelectedReviews([]); - pullLatestData(); - }, [currentItems, minimapBounds]); + axios.post( + `export/${review.camera}/start/${review.start_time}/end/${review.end_time}`, + { playback: "realtime" }, + ); + }, + [selectedReviews], + ); if (!config) { return ; @@ -252,7 +245,7 @@ export default function EventView({ return (
-
+
{isMobile && ( )} @@ -290,11 +283,14 @@ export default function EventView({
Motion
- - {selectedReviews.length > 0 && ( + + {selectedReviews.length <= 0 ? ( + + ) : ( )} @@ -347,7 +343,6 @@ export default function EventView({ allPreviews={relevantPreviews} setReviewed={markItemAsReviewed} onTimeUpdate={setPreviewTime} - markAboveReviewed={markScrolledItemsAsReviewed} onClick={onSelectReview} />