Add motion previews filter (#22347)

* add ability to filter motion previews via heatmap grid

* i18n

* use dialog on mobile
This commit is contained in:
Josh Hawkins
2026-03-09 14:14:13 -06:00
committed by GitHub
parent dd9497baf2
commit 9cbd80d981
4 changed files with 259 additions and 4 deletions
+81 -1
View File
@@ -79,7 +79,17 @@ import { GiSoundWaves } from "react-icons/gi";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { useTimelineZoom } from "@/hooks/use-timeline-zoom";
import { useTranslation } from "react-i18next";
import { FaCog } from "react-icons/fa";
import { FaCog, FaFilter } from "react-icons/fa";
import MotionRegionFilterGrid from "@/components/filter/MotionRegionFilterGrid";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import ReviewActivityCalendar from "@/components/overlay/ReviewActivityCalendar";
import PlatformAwareDialog from "@/components/overlay/dialog/PlatformAwareDialog";
import MotionPreviewsPane from "./MotionPreviewsPane";
@@ -1117,6 +1127,13 @@ function MotionReview({
const [controlsOpen, setControlsOpen] = useState(false);
const [dimStrength, setDimStrength] = useState(82);
const [isPreviewSettingsOpen, setIsPreviewSettingsOpen] = useState(false);
const [motionFilterCells, setMotionFilterCells] = useState<Set<number>>(
new Set(),
);
const [pendingFilterCells, setPendingFilterCells] = useState<Set<number>>(
new Set(),
);
const [isRegionFilterOpen, setIsRegionFilterOpen] = useState(false);
const objectReviewItems = useMemo(
() =>
@@ -1314,6 +1331,68 @@ function MotionReview({
updateSelectedDay={onUpdateSelectedDay}
/>
)}
<Dialog
open={isRegionFilterOpen}
onOpenChange={(open) => {
if (open) {
setPendingFilterCells(new Set(motionFilterCells));
}
setIsRegionFilterOpen(open);
}}
>
<DialogTrigger asChild>
<Button
className={cn(
isDesktop ? "flex items-center gap-2" : "rounded-lg",
)}
size="sm"
variant={motionFilterCells.size > 0 ? "select" : "default"}
aria-label={t("motionPreviews.filter")}
>
<FaFilter
className={
motionFilterCells.size > 0
? "text-selected-foreground"
: "text-secondary-foreground"
}
/>
{isDesktop && t("motionPreviews.filter")}
</Button>
</DialogTrigger>
<DialogContent className="max-h-[90dvh] overflow-y-auto sm:max-w-[85%] md:max-w-[70%] lg:max-w-[60%]">
<DialogHeader>
<DialogTitle>{t("motionPreviews.filter")}</DialogTitle>
<DialogDescription>
{t("motionPreviews.filterDesc")}
</DialogDescription>
</DialogHeader>
<MotionRegionFilterGrid
cameraName={selectedMotionPreviewCamera.name}
selectedCells={pendingFilterCells}
onCellsChange={setPendingFilterCells}
/>
<DialogFooter className="justify-end gap-1">
<Button
variant="outline"
disabled={pendingFilterCells.size === 0}
onClick={() => {
setPendingFilterCells(new Set());
}}
>
{t("motionPreviews.filterClear")}
</Button>
<Button
variant="select"
onClick={() => {
setMotionFilterCells(new Set(pendingFilterCells));
setIsRegionFilterOpen(false);
}}
>
{t("button.apply", { ns: "common" })}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<PlatformAwareDialog
trigger={
<Button
@@ -1456,6 +1535,7 @@ function MotionReview({
}
playbackRate={playbackRate}
nonMotionAlpha={dimStrength / 100}
motionFilterCells={motionFilterCells}
onSeek={(timestamp) => {
onOpenRecording({
camera: selectedMotionPreviewCamera.name,