Get actions looking good on mobile and desktop

This commit is contained in:
Nicolas Mowen 2024-03-01 08:32:10 -07:00
parent f4c427fa98
commit 95ceff69ea
3 changed files with 42 additions and 158 deletions

View File

@ -2,6 +2,7 @@ import { LuCheckSquare, LuFileUp, LuTrash } from "react-icons/lu";
import { useCallback } from "react"; import { useCallback } from "react";
import axios from "axios"; import axios from "axios";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { isDesktop } from "react-device-detect";
type ReviewActionGroupProps = { type ReviewActionGroupProps = {
selectedReviews: string[]; selectedReviews: string[];
@ -34,43 +35,47 @@ export default function ReviewActionGroup({
}, [selectedReviews, setSelectedReviews, pullLatestData]); }, [selectedReviews, setSelectedReviews, pullLatestData]);
return ( return (
<div className="absolute inset-x-2 inset-y-0 md:left-auto md:right-2 p-2 flex gap-2 justify-between items-center"> <div className="absolute inset-x-2 inset-y-0 md:left-auto md:right-2 p-2 flex gap-2 justify-between items-center bg-background">
<div className="text-sm text-gray-500">{`${selectedReviews.length} selected | `}</div> <div className="flex items-center">
<Button size="sm" variant="link" onClick={onClearSelected}> <div className="text-sm text-gray-500 mr-2">{`${selectedReviews.length} selected | `}</div>
Unselect <Button size="xs" variant="link" onClick={onClearSelected}>
</Button> Unselect
{selectedReviews.length == 1 && ( </Button>
</div>
<div className="flex items-center gap-1 md:gap-2">
{selectedReviews.length == 1 && (
<Button
className="flex items-center"
variant="secondary"
size="sm"
onClick={() => {
onExport(selectedReviews[0]);
onClearSelected();
}}
>
<LuFileUp className="mr-1" />
{isDesktop && "Export"}
</Button>
)}
<Button <Button
className="flex items-center" className="flex items-center"
variant="secondary" variant="secondary"
size="sm" size="sm"
onClick={() => { onClick={onMarkAsReviewed}
onExport(selectedReviews[0]);
onClearSelected();
}}
> >
<LuFileUp className="mr-1" /> <LuCheckSquare className="mr-1" />
Export {isDesktop && "Mark as reviewed"}
</Button> </Button>
)} <Button
<Button className="flex items-center"
className="flex items-center" variant="secondary"
variant="secondary" size="sm"
size="sm" onClick={onDelete}
onClick={onMarkAsReviewed} >
> <LuTrash className="mr-1" />
<LuCheckSquare className="mr-1" /> {isDesktop && "Delete"}
Mark as reviewed </Button>
</Button> </div>
<Button
className="flex items-center"
variant="secondary"
size="sm"
onClick={onDelete}
>
<LuTrash className="mr-1" />
Delete
</Button>
</div> </div>
); );
} }

View File

@ -13,18 +13,8 @@ import { getIconForLabel, getIconForSubLabel } from "@/utils/iconUtil";
import TimeAgo from "../dynamic/TimeAgo"; import TimeAgo from "../dynamic/TimeAgo";
import useSWR from "swr"; import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { isDesktop, isFirefox, isMobile, isSafari } from "react-device-detect"; import { isFirefox, isMobile, isSafari } from "react-device-detect";
import Chip from "../Chip"; 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 { useFormattedTimestamp } from "@/hooks/use-date-utils";
import useImageLoaded from "@/hooks/use-image-loaded"; import useImageLoaded from "@/hooks/use-image-loaded";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
@ -35,7 +25,6 @@ type PreviewPlayerProps = {
allPreviews?: Preview[]; allPreviews?: Preview[];
onTimeUpdate?: React.Dispatch<React.SetStateAction<number | undefined>>; onTimeUpdate?: React.Dispatch<React.SetStateAction<number | undefined>>;
setReviewed: (reviewId: string) => void; setReviewed: (reviewId: string) => void;
markAboveReviewed: () => void;
onClick: (reviewId: string, ctrl: boolean) => void; onClick: (reviewId: string, ctrl: boolean) => void;
}; };
@ -51,7 +40,6 @@ export default function PreviewThumbnailPlayer({
review, review,
allPreviews, allPreviews,
setReviewed, setReviewed,
markAboveReviewed,
onClick, onClick,
onTimeUpdate, onTimeUpdate,
}: PreviewPlayerProps) { }: PreviewPlayerProps) {
@ -166,19 +154,15 @@ export default function PreviewThumbnailPlayer({
config?.ui.time_format == "24hour" ? "%b %-d, %H:%M" : "%b %-d, %I:%M %p", config?.ui.time_format == "24hour" ? "%b %-d, %H:%M" : "%b %-d, %I:%M %p",
); );
const previewContent = ( return (
<div <div
className="relative size-full cursor-pointer" className="relative size-full cursor-pointer"
onMouseEnter={isMobile ? undefined : () => onPlayback(true)} onMouseEnter={isMobile ? undefined : () => onPlayback(true)}
onMouseLeave={isMobile ? undefined : () => onPlayback(false)} onMouseLeave={isMobile ? undefined : () => onPlayback(false)}
onContextMenu={ onContextMenu={(e) => {
isDesktop e.preventDefault();
? undefined onClick(review.id, true);
: (e) => { }}
e.preventDefault();
onClick(review.id, true);
}
}
onClick={handleOnClick} onClick={handleOnClick}
{...swipeHandlers} {...swipeHandlers}
> >
@ -242,22 +226,6 @@ export default function PreviewThumbnailPlayer({
)} )}
</div> </div>
); );
if (isDesktop) {
return (
<ContextMenu>
<ContextMenuTrigger asChild>{previewContent}</ContextMenuTrigger>
<PreviewContextItems
review={review}
onSelect={() => onClick(review.id, true)}
setReviewed={handleSetReviewed}
markAboveReviewed={markAboveReviewed}
/>
</ContextMenu>
);
}
return previewContent;
} }
type PreviewContentProps = { type PreviewContentProps = {
@ -624,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 (
<ContextMenuContent>
{isMobile && (
<ContextMenuItem onSelect={onSelect}>
<div className="w-full flex justify-between items-center">
Select
<RiCheckboxMultipleLine className="ml-4 size-4" />
</div>
</ContextMenuItem>
)}
<ContextMenuItem onSelect={markAboveReviewed}>
<div className="w-full flex justify-between items-center">
Mark Above as Reviewed
<LuCheckCheck className="ml-4 size-4" />
</div>
</ContextMenuItem>
<ContextMenuSeparator />
{!review.has_been_reviewed && (
<ContextMenuItem onSelect={() => (setReviewed ? setReviewed() : null)}>
<div className="w-full flex justify-between items-center">
Mark As Reviewed
<LuCheckSquare className="ml-4 size-4" />
</div>
</ContextMenuItem>
)}
<ContextMenuItem onSelect={exportReview}>
<div className="w-full flex justify-between items-center">
Export
<LuFileUp className="ml-4 size-4" />
</div>
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem onSelect={deleteReview}>
<div className="w-full flex justify-between items-center text-danger">
Delete
<LuTrash className="ml-4 size-4" />
</div>
</ContextMenuItem>
</ContextMenuContent>
);
}
function PreviewPlaceholder({ imgLoaded }: { imgLoaded: boolean }) { function PreviewPlaceholder({ imgLoaded }: { imgLoaded: boolean }) {
if (imgLoaded) { if (imgLoaded) {
return; return;

View File

@ -223,29 +223,6 @@ export default function EventView({
[selectedReviews, setSelectedReviews], [selectedReviews, setSelectedReviews],
); );
const markScrolledItemsAsReviewed = useCallback(async () => {
if (!currentItems) {
return;
}
const scrolled: string[] = [];
currentItems.find((value) => {
if (value.start_time > minimapBounds.end) {
scrolled.push(value.id);
return false;
} else {
return true;
}
});
const idList = scrolled.join(",");
await axios.post(`reviews/${idList}/viewed`);
setSelectedReviews([]);
pullLatestData();
}, [currentItems, minimapBounds]);
const exportReview = useCallback( const exportReview = useCallback(
(id: string) => { (id: string) => {
const review = currentItems?.find((seg) => seg.id == id); const review = currentItems?.find((seg) => seg.id == id);
@ -366,7 +343,6 @@ export default function EventView({
allPreviews={relevantPreviews} allPreviews={relevantPreviews}
setReviewed={markItemAsReviewed} setReviewed={markItemAsReviewed}
onTimeUpdate={setPreviewTime} onTimeUpdate={setPreviewTime}
markAboveReviewed={markScrolledItemsAsReviewed}
onClick={onSelectReview} onClick={onSelectReview}
/> />
</div> </div>