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,
|
||||
adding: true,
|
||||
});
|
||||
const lastCellRef = useRef<number>(-1);
|
||||
const gridRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const toggleCell = useCallback(
|
||||
(index: number, forceAdd?: boolean) => {
|
||||
@ -40,28 +42,68 @@ export default function MotionRegionFilterGrid({
|
||||
[selectedCells, onCellsChange],
|
||||
);
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(index: number) => {
|
||||
const adding = !selectedCells.has(index);
|
||||
paintingRef.current = { active: true, adding };
|
||||
toggleCell(index, adding);
|
||||
const getCellFromPoint = useCallback(
|
||||
(clientX: number, clientY: number): number | null => {
|
||||
const grid = gridRef.current;
|
||||
|
||||
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(
|
||||
(index: number) => {
|
||||
const handlePointerDown = useCallback(
|
||||
(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) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = getCellFromPoint(e.clientX, e.clientY);
|
||||
|
||||
if (index === null || index === lastCellRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastCellRef.current = index;
|
||||
toggleCell(index, paintingRef.current.adding);
|
||||
},
|
||||
[toggleCell],
|
||||
[toggleCell, getCellFromPoint],
|
||||
);
|
||||
|
||||
const handlePointerUp = useCallback(() => {
|
||||
paintingRef.current.active = false;
|
||||
lastCellRef.current = -1;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -79,11 +121,14 @@ export default function MotionRegionFilterGrid({
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
ref={gridRef}
|
||||
className="absolute inset-0 grid"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${GRID_SIZE}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${GRID_SIZE}, 1fr)`,
|
||||
}}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
>
|
||||
{Array.from({ length: GRID_SIZE * GRID_SIZE }, (_, 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-transparent hover:bg-white/20"
|
||||
}
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault();
|
||||
handlePointerDown(index);
|
||||
}}
|
||||
onPointerEnter={() => handlePointerEnter(index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -90,7 +90,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
|
||||
import ReviewActivityCalendar from "@/components/overlay/ReviewActivityCalendar";
|
||||
import PlatformAwareDialog from "@/components/overlay/dialog/PlatformAwareDialog";
|
||||
import MotionPreviewsPane from "./MotionPreviewsPane";
|
||||
@ -1332,7 +1331,6 @@ function MotionReview({
|
||||
updateSelectedDay={onUpdateSelectedDay}
|
||||
/>
|
||||
)}
|
||||
{isDesktop ? (
|
||||
<Dialog
|
||||
open={isRegionFilterOpen}
|
||||
onOpenChange={(open) => {
|
||||
@ -1344,11 +1342,11 @@ function MotionReview({
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
className={cn(
|
||||
isDesktop ? "flex items-center gap-2" : "rounded-lg",
|
||||
)}
|
||||
size="sm"
|
||||
variant={
|
||||
motionFilterCells.size > 0 ? "select" : "default"
|
||||
}
|
||||
variant={motionFilterCells.size > 0 ? "select" : "default"}
|
||||
aria-label={t("motionPreviews.filter")}
|
||||
>
|
||||
<FaFilter
|
||||
@ -1358,7 +1356,7 @@ function MotionReview({
|
||||
: "text-secondary-foreground"
|
||||
}
|
||||
/>
|
||||
{t("motionPreviews.filter")}
|
||||
{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%]">
|
||||
@ -1395,72 +1393,6 @@ function MotionReview({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</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
|
||||
trigger={
|
||||
<Button
|
||||
|
||||
Loading…
Reference in New Issue
Block a user