mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 02:29:19 +03:00
use dialog on mobile
This commit is contained in:
parent
6329559d5f
commit
33c9d2345a
@ -18,6 +18,8 @@ export default function MotionRegionFilterGrid({
|
|||||||
active: false,
|
active: false,
|
||||||
adding: true,
|
adding: true,
|
||||||
});
|
});
|
||||||
|
const lastCellRef = useRef<number>(-1);
|
||||||
|
const gridRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const toggleCell = useCallback(
|
const toggleCell = useCallback(
|
||||||
(index: number, forceAdd?: boolean) => {
|
(index: number, forceAdd?: boolean) => {
|
||||||
@ -40,28 +42,68 @@ export default function MotionRegionFilterGrid({
|
|||||||
[selectedCells, onCellsChange],
|
[selectedCells, onCellsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePointerDown = useCallback(
|
const getCellFromPoint = useCallback(
|
||||||
(index: number) => {
|
(clientX: number, clientY: number): number | null => {
|
||||||
const adding = !selectedCells.has(index);
|
const grid = gridRef.current;
|
||||||
paintingRef.current = { active: true, adding };
|
|
||||||
toggleCell(index, adding);
|
if (!grid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = grid.getBoundingClientRect();
|
||||||
|
const x = clientX - rect.left;
|
||||||
|
const y = clientY - rect.top;
|
||||||
|
|
||||||
|
if (x < 0 || y < 0 || x >= rect.width || y >= rect.height) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const col = Math.floor((x / rect.width) * GRID_SIZE);
|
||||||
|
const row = Math.floor((y / rect.height) * GRID_SIZE);
|
||||||
|
|
||||||
|
return row * GRID_SIZE + col;
|
||||||
},
|
},
|
||||||
[selectedCells, toggleCell],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePointerEnter = useCallback(
|
const handlePointerDown = useCallback(
|
||||||
(index: number) => {
|
(e: React.PointerEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const index = getCellFromPoint(e.clientX, e.clientY);
|
||||||
|
|
||||||
|
if (index === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const adding = !selectedCells.has(index);
|
||||||
|
paintingRef.current = { active: true, adding };
|
||||||
|
lastCellRef.current = index;
|
||||||
|
toggleCell(index, adding);
|
||||||
|
},
|
||||||
|
[selectedCells, toggleCell, getCellFromPoint],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePointerMove = useCallback(
|
||||||
|
(e: React.PointerEvent) => {
|
||||||
if (!paintingRef.current.active) {
|
if (!paintingRef.current.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const index = getCellFromPoint(e.clientX, e.clientY);
|
||||||
|
|
||||||
|
if (index === null || index === lastCellRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCellRef.current = index;
|
||||||
toggleCell(index, paintingRef.current.adding);
|
toggleCell(index, paintingRef.current.adding);
|
||||||
},
|
},
|
||||||
[toggleCell],
|
[toggleCell, getCellFromPoint],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePointerUp = useCallback(() => {
|
const handlePointerUp = useCallback(() => {
|
||||||
paintingRef.current.active = false;
|
paintingRef.current.active = false;
|
||||||
|
lastCellRef.current = -1;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -79,11 +121,14 @@ export default function MotionRegionFilterGrid({
|
|||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
ref={gridRef}
|
||||||
className="absolute inset-0 grid"
|
className="absolute inset-0 grid"
|
||||||
style={{
|
style={{
|
||||||
gridTemplateColumns: `repeat(${GRID_SIZE}, 1fr)`,
|
gridTemplateColumns: `repeat(${GRID_SIZE}, 1fr)`,
|
||||||
gridTemplateRows: `repeat(${GRID_SIZE}, 1fr)`,
|
gridTemplateRows: `repeat(${GRID_SIZE}, 1fr)`,
|
||||||
}}
|
}}
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
onPointerMove={handlePointerMove}
|
||||||
>
|
>
|
||||||
{Array.from({ length: GRID_SIZE * GRID_SIZE }, (_, index) => {
|
{Array.from({ length: GRID_SIZE * GRID_SIZE }, (_, index) => {
|
||||||
const isSelected = selectedCells.has(index);
|
const isSelected = selectedCells.has(index);
|
||||||
@ -95,11 +140,6 @@ export default function MotionRegionFilterGrid({
|
|||||||
? "border border-severity_alert/60 bg-severity_alert/40"
|
? "border border-severity_alert/60 bg-severity_alert/40"
|
||||||
: "border border-transparent hover:bg-white/20"
|
: "border border-transparent hover:bg-white/20"
|
||||||
}
|
}
|
||||||
onPointerDown={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handlePointerDown(index);
|
|
||||||
}}
|
|
||||||
onPointerEnter={() => handlePointerEnter(index)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -90,7 +90,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
|
|
||||||
import ReviewActivityCalendar from "@/components/overlay/ReviewActivityCalendar";
|
import ReviewActivityCalendar from "@/components/overlay/ReviewActivityCalendar";
|
||||||
import PlatformAwareDialog from "@/components/overlay/dialog/PlatformAwareDialog";
|
import PlatformAwareDialog from "@/components/overlay/dialog/PlatformAwareDialog";
|
||||||
import MotionPreviewsPane from "./MotionPreviewsPane";
|
import MotionPreviewsPane from "./MotionPreviewsPane";
|
||||||
@ -1332,7 +1331,6 @@ function MotionReview({
|
|||||||
updateSelectedDay={onUpdateSelectedDay}
|
updateSelectedDay={onUpdateSelectedDay}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isDesktop ? (
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isRegionFilterOpen}
|
open={isRegionFilterOpen}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
@ -1344,11 +1342,11 @@ function MotionReview({
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2"
|
className={cn(
|
||||||
|
isDesktop ? "flex items-center gap-2" : "rounded-lg",
|
||||||
|
)}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant={
|
variant={motionFilterCells.size > 0 ? "select" : "default"}
|
||||||
motionFilterCells.size > 0 ? "select" : "default"
|
|
||||||
}
|
|
||||||
aria-label={t("motionPreviews.filter")}
|
aria-label={t("motionPreviews.filter")}
|
||||||
>
|
>
|
||||||
<FaFilter
|
<FaFilter
|
||||||
@ -1358,7 +1356,7 @@ function MotionReview({
|
|||||||
: "text-secondary-foreground"
|
: "text-secondary-foreground"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{t("motionPreviews.filter")}
|
{isDesktop && t("motionPreviews.filter")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-[90dvh] overflow-y-auto sm:max-w-[85%] md:max-w-[70%] lg:max-w-[60%]">
|
<DialogContent className="max-h-[90dvh] overflow-y-auto sm:max-w-[85%] md:max-w-[70%] lg:max-w-[60%]">
|
||||||
@ -1395,72 +1393,6 @@ function MotionReview({
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
) : (
|
|
||||||
<Drawer
|
|
||||||
open={isRegionFilterOpen}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
if (open) {
|
|
||||||
setPendingFilterCells(new Set(motionFilterCells));
|
|
||||||
}
|
|
||||||
setIsRegionFilterOpen(open);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DrawerTrigger asChild>
|
|
||||||
<Button
|
|
||||||
className="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"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</DrawerTrigger>
|
|
||||||
<DrawerContent className="max-h-[75dvh] overflow-hidden px-4 pb-4">
|
|
||||||
<div className="space-y-4 py-2">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<div className="text-md">
|
|
||||||
{t("motionPreviews.filter")}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-muted-foreground">
|
|
||||||
{t("motionPreviews.filterDesc")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<MotionRegionFilterGrid
|
|
||||||
cameraName={selectedMotionPreviewCamera.name}
|
|
||||||
selectedCells={pendingFilterCells}
|
|
||||||
onCellsChange={setPendingFilterCells}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
disabled={pendingFilterCells.size === 0}
|
|
||||||
onClick={() => {
|
|
||||||
setPendingFilterCells(new Set());
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("motionPreviews.filterClear")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setMotionFilterCells(new Set(pendingFilterCells));
|
|
||||||
setIsRegionFilterOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("button.apply", { ns: "common" })}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DrawerContent>
|
|
||||||
</Drawer>
|
|
||||||
)}
|
|
||||||
<PlatformAwareDialog
|
<PlatformAwareDialog
|
||||||
trigger={
|
trigger={
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user