mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-10 21:25:24 +03:00
tweaks
This commit is contained in:
parent
f92de9af2e
commit
8e24bd79df
@ -236,7 +236,7 @@ export function useMotionContourArea(camera: string): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useImproveContrast(camera: string): {
|
export function useImproveContrast(camera: string): {
|
||||||
payload: string;
|
payload: ToggleableSetting;
|
||||||
send: (payload: string, retain?: boolean) => void;
|
send: (payload: string, retain?: boolean) => void;
|
||||||
} {
|
} {
|
||||||
const {
|
const {
|
||||||
@ -246,5 +246,5 @@ export function useImproveContrast(camera: string): {
|
|||||||
`${camera}/improve_contrast/state`,
|
`${camera}/improve_contrast/state`,
|
||||||
`${camera}/improve_contrast/set`,
|
`${camera}/improve_contrast/set`,
|
||||||
);
|
);
|
||||||
return { payload: payload as string, send };
|
return { payload: payload as ToggleableSetting, send };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import MotionMaskEditPane from "./MotionMaskEditPane";
|
|||||||
import ObjectMaskEditPane from "./ObjectMaskEditPane";
|
import ObjectMaskEditPane from "./ObjectMaskEditPane";
|
||||||
import PolygonItem from "./PolygonItem";
|
import PolygonItem from "./PolygonItem";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { isDesktop } from "react-device-detect";
|
||||||
|
|
||||||
type MasksAndZoneProps = {
|
type MasksAndZoneProps = {
|
||||||
selectedCamera: string;
|
selectedCamera: string;
|
||||||
@ -87,8 +88,8 @@ export default function MasksAndZones({
|
|||||||
}, [config, selectedCamera]);
|
}, [config, selectedCamera]);
|
||||||
|
|
||||||
const stretch = true;
|
const stretch = true;
|
||||||
// TODO: mobile / portrait cams
|
// may need tweaking for mobile
|
||||||
const fitAspect = 16 / 9;
|
const fitAspect = isDesktop ? 16 / 9 : 3 / 4;
|
||||||
|
|
||||||
const scaledHeight = useMemo(() => {
|
const scaledHeight = useMemo(() => {
|
||||||
if (containerRef.current && aspectRatio && detectHeight) {
|
if (containerRef.current && aspectRatio && detectHeight) {
|
||||||
@ -97,7 +98,9 @@ export default function MasksAndZones({
|
|||||||
? Math.floor(
|
? Math.floor(
|
||||||
Math.min(containerHeight, containerRef.current?.clientHeight),
|
Math.min(containerHeight, containerRef.current?.clientHeight),
|
||||||
)
|
)
|
||||||
: Math.floor(containerWidth / aspectRatio);
|
: isDesktop || aspectRatio > fitAspect
|
||||||
|
? Math.floor(containerWidth / aspectRatio)
|
||||||
|
: Math.floor(containerWidth / aspectRatio) / 1.5;
|
||||||
const finalHeight = stretch
|
const finalHeight = stretch
|
||||||
? scaledHeight
|
? scaledHeight
|
||||||
: Math.min(scaledHeight, detectHeight);
|
: Math.min(scaledHeight, detectHeight);
|
||||||
@ -157,12 +160,14 @@ export default function MasksAndZones({
|
|||||||
setEditingPolygons([...allPolygons]);
|
setEditingPolygons([...allPolygons]);
|
||||||
setActivePolygonIndex(undefined);
|
setActivePolygonIndex(undefined);
|
||||||
setHoveredPolygonIndex(null);
|
setHoveredPolygonIndex(null);
|
||||||
}, [allPolygons]);
|
setUnsavedChanges(false);
|
||||||
|
}, [allPolygons, setUnsavedChanges]);
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
const handleSave = useCallback(() => {
|
||||||
setAllPolygons([...(editingPolygons ?? [])]);
|
setAllPolygons([...(editingPolygons ?? [])]);
|
||||||
setHoveredPolygonIndex(null);
|
setHoveredPolygonIndex(null);
|
||||||
}, [editingPolygons]);
|
setUnsavedChanges(false);
|
||||||
|
}, [editingPolygons, setUnsavedChanges]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@ -218,62 +223,52 @@ export default function MasksAndZones({
|
|||||||
let globalObjectMasks: Polygon[] = [];
|
let globalObjectMasks: Polygon[] = [];
|
||||||
let objectMasks: Polygon[] = [];
|
let objectMasks: Polygon[] = [];
|
||||||
|
|
||||||
if (
|
// this can be an array or a string
|
||||||
cameraConfig.motion.mask !== null &&
|
motionMasks = (
|
||||||
cameraConfig.motion.mask !== undefined
|
Array.isArray(cameraConfig.motion.mask)
|
||||||
) {
|
? cameraConfig.motion.mask
|
||||||
// this can be an array or a string
|
: cameraConfig.motion.mask
|
||||||
motionMasks = (
|
? [cameraConfig.motion.mask]
|
||||||
Array.isArray(cameraConfig.motion.mask)
|
: []
|
||||||
? cameraConfig.motion.mask
|
).map((maskData, index) => ({
|
||||||
: cameraConfig.motion.mask
|
type: "motion_mask" as PolygonType,
|
||||||
? [cameraConfig.motion.mask]
|
typeIndex: index,
|
||||||
: []
|
camera: cameraConfig.name,
|
||||||
).map((maskData, index) => ({
|
name: `Motion Mask ${index + 1}`,
|
||||||
type: "motion_mask" as PolygonType,
|
objects: [],
|
||||||
typeIndex: index,
|
points: interpolatePoints(
|
||||||
camera: cameraConfig.name,
|
parseCoordinates(maskData),
|
||||||
name: `Motion Mask ${index + 1}`,
|
1,
|
||||||
objects: [],
|
1,
|
||||||
points: interpolatePoints(
|
scaledWidth,
|
||||||
parseCoordinates(maskData),
|
scaledHeight,
|
||||||
1,
|
),
|
||||||
1,
|
isFinished: true,
|
||||||
scaledWidth,
|
color: [0, 0, 255],
|
||||||
scaledHeight,
|
}));
|
||||||
),
|
|
||||||
isFinished: true,
|
|
||||||
color: [0, 0, 255],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalObjectMasksArray = Array.isArray(cameraConfig.objects.mask)
|
const globalObjectMasksArray = Array.isArray(cameraConfig.objects.mask)
|
||||||
? cameraConfig.objects.mask
|
? cameraConfig.objects.mask
|
||||||
: cameraConfig.objects.mask
|
: cameraConfig.objects.mask
|
||||||
? [cameraConfig.objects.mask]
|
? [cameraConfig.objects.mask]
|
||||||
: [];
|
: [];
|
||||||
// TODO: check to see if this is necessary
|
|
||||||
if (
|
globalObjectMasks = globalObjectMasksArray.map((maskData, index) => ({
|
||||||
cameraConfig.objects.mask !== null &&
|
type: "object_mask" as PolygonType,
|
||||||
cameraConfig.objects.mask !== undefined
|
typeIndex: index,
|
||||||
) {
|
camera: cameraConfig.name,
|
||||||
globalObjectMasks = globalObjectMasksArray.map((maskData, index) => ({
|
name: `Object Mask ${index + 1} (all objects)`,
|
||||||
type: "object_mask" as PolygonType,
|
objects: [],
|
||||||
typeIndex: index,
|
points: interpolatePoints(
|
||||||
camera: cameraConfig.name,
|
parseCoordinates(maskData),
|
||||||
name: `Object Mask ${index + 1} (all objects)`,
|
1,
|
||||||
objects: [],
|
1,
|
||||||
points: interpolatePoints(
|
scaledWidth,
|
||||||
parseCoordinates(maskData),
|
scaledHeight,
|
||||||
1,
|
),
|
||||||
1,
|
isFinished: true,
|
||||||
scaledWidth,
|
color: [128, 128, 128],
|
||||||
scaledHeight,
|
}));
|
||||||
),
|
|
||||||
isFinished: true,
|
|
||||||
color: [128, 128, 128],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalObjectMasksCount = globalObjectMasks.length;
|
const globalObjectMasksCount = globalObjectMasks.length;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
|||||||
@ -1,14 +1,4 @@
|
|||||||
import Heading from "@/components/ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
} from "../ui/alert-dialog";
|
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@ -50,7 +40,6 @@ export default function MotionTuner({
|
|||||||
useSWR<FrigateConfig>("config");
|
useSWR<FrigateConfig>("config");
|
||||||
const [changedValue, setChangedValue] = useState(false);
|
const [changedValue, setChangedValue] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
|
||||||
|
|
||||||
const { send: sendMotionThreshold } = useMotionThreshold(selectedCamera);
|
const { send: sendMotionThreshold } = useMotionThreshold(selectedCamera);
|
||||||
const { send: sendMotionContourArea } = useMotionContourArea(selectedCamera);
|
const { send: sendMotionContourArea } = useMotionContourArea(selectedCamera);
|
||||||
@ -87,43 +76,31 @@ export default function MotionTuner({
|
|||||||
improve_contrast: cameraConfig.motion.improve_contrast,
|
improve_contrast: cameraConfig.motion.improve_contrast,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [cameraConfig]);
|
// we know that these deps are correct
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cameraConfig) {
|
if (!motionSettings.threshold) return;
|
||||||
const { threshold, contour_area, improve_contrast } = motionSettings;
|
|
||||||
|
|
||||||
if (
|
sendMotionThreshold(motionSettings.threshold);
|
||||||
threshold !== undefined &&
|
}, [motionSettings.threshold, sendMotionThreshold]);
|
||||||
cameraConfig.motion.threshold !== threshold
|
|
||||||
) {
|
|
||||||
sendMotionThreshold(threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
useEffect(() => {
|
||||||
contour_area !== undefined &&
|
if (!motionSettings.contour_area) return;
|
||||||
cameraConfig.motion.contour_area !== contour_area
|
|
||||||
) {
|
|
||||||
sendMotionContourArea(contour_area);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
sendMotionContourArea(motionSettings.contour_area);
|
||||||
improve_contrast !== undefined &&
|
}, [motionSettings.contour_area, sendMotionContourArea]);
|
||||||
cameraConfig.motion.improve_contrast !== improve_contrast
|
|
||||||
) {
|
useEffect(() => {
|
||||||
sendImproveContrast(improve_contrast ? "ON" : "OFF");
|
if (motionSettings.improve_contrast === undefined) return;
|
||||||
}
|
|
||||||
}
|
sendImproveContrast(motionSettings.improve_contrast ? "ON" : "OFF");
|
||||||
}, [
|
}, [motionSettings.improve_contrast, sendImproveContrast]);
|
||||||
cameraConfig,
|
|
||||||
motionSettings,
|
|
||||||
sendMotionThreshold,
|
|
||||||
sendMotionContourArea,
|
|
||||||
sendImproveContrast,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleMotionConfigChange = (newConfig: Partial<MotionSettings>) => {
|
const handleMotionConfigChange = (newConfig: Partial<MotionSettings>) => {
|
||||||
setMotionSettings((prevConfig) => ({ ...prevConfig, ...newConfig }));
|
setMotionSettings((prevConfig) => ({ ...prevConfig, ...newConfig }));
|
||||||
|
setUnsavedChanges(true);
|
||||||
setChangedValue(true);
|
setChangedValue(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -170,17 +147,6 @@ export default function MotionTuner({
|
|||||||
setChangedValue(false);
|
setChangedValue(false);
|
||||||
}, [origMotionSettings]);
|
}, [origMotionSettings]);
|
||||||
|
|
||||||
const handleDialog = useCallback(
|
|
||||||
(save: boolean) => {
|
|
||||||
if (save) {
|
|
||||||
saveToConfig();
|
|
||||||
}
|
|
||||||
setConfirmationDialogOpen(false);
|
|
||||||
setChangedValue(false);
|
|
||||||
},
|
|
||||||
[saveToConfig],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!cameraConfig && !selectedCamera) {
|
if (!cameraConfig && !selectedCamera) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
@ -315,31 +281,6 @@ export default function MotionTuner({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{confirmationDialogOpen && (
|
|
||||||
<AlertDialog
|
|
||||||
open={confirmationDialogOpen}
|
|
||||||
onOpenChange={() => setConfirmationDialogOpen(false)}
|
|
||||||
>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>
|
|
||||||
You have unsaved changes on this camera.
|
|
||||||
</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
Do you want to save your changes before continuing?
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel onClick={() => handleDialog(false)}>
|
|
||||||
Cancel
|
|
||||||
</AlertDialogCancel>
|
|
||||||
<AlertDialogAction onClick={() => handleDialog(true)}>
|
|
||||||
Save
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{cameraConfig ? (
|
{cameraConfig ? (
|
||||||
|
|||||||
@ -97,6 +97,7 @@ export default function PolygonDrawer({
|
|||||||
onMouseOver={isActive ? handleGroupMouseOver : undefined}
|
onMouseOver={isActive ? handleGroupMouseOver : undefined}
|
||||||
onTouchStart={isActive ? handleGroupMouseOver : undefined}
|
onTouchStart={isActive ? handleGroupMouseOver : undefined}
|
||||||
onMouseOut={isActive ? handleGroupMouseOut : undefined}
|
onMouseOut={isActive ? handleGroupMouseOut : undefined}
|
||||||
|
// TODO: don't use zindex
|
||||||
zIndex={isActive ? 999 : 100}
|
zIndex={isActive ? 999 : 100}
|
||||||
>
|
>
|
||||||
<Line
|
<Line
|
||||||
|
|||||||
@ -53,12 +53,12 @@ export default function PolygonEditControls({
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="default"
|
||||||
className="size-6 p-1 rounded-md text-background bg-secondary-foreground"
|
className="size-6 p-1 rounded-md"
|
||||||
disabled={!polygons[activePolygonIndex].points.length}
|
disabled={!polygons[activePolygonIndex].points.length}
|
||||||
onClick={undo}
|
onClick={undo}
|
||||||
>
|
>
|
||||||
<MdUndo />
|
<MdUndo className="text-secondary-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Undo</TooltipContent>
|
<TooltipContent>Undo</TooltipContent>
|
||||||
@ -66,12 +66,12 @@ export default function PolygonEditControls({
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="default"
|
||||||
className="size-6 p-1 rounded-md text-background bg-secondary-foreground"
|
className="size-6 p-1 rounded-md"
|
||||||
disabled={!polygons[activePolygonIndex].points.length}
|
disabled={!polygons[activePolygonIndex].points.length}
|
||||||
onClick={reset}
|
onClick={reset}
|
||||||
>
|
>
|
||||||
<MdOutlineRestartAlt />
|
<MdOutlineRestartAlt className="text-secondary-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Reset</TooltipContent>
|
<TooltipContent>Reset</TooltipContent>
|
||||||
|
|||||||
@ -5,12 +5,22 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||||
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
|
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
|
||||||
import MotionTuner from "@/components/settings/MotionTuner";
|
import MotionTuner from "@/components/settings/MotionTuner";
|
||||||
import MasksAndZones from "@/components/settings/MasksAndZones";
|
import MasksAndZones from "@/components/settings/MasksAndZones";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import useOptimisticState from "@/hooks/use-optimistic-state";
|
import useOptimisticState from "@/hooks/use-optimistic-state";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import { FaVideo } from "react-icons/fa";
|
import { FaVideo } from "react-icons/fa";
|
||||||
@ -38,6 +48,7 @@ export default function Settings() {
|
|||||||
|
|
||||||
// TODO: confirm leave page
|
// TODO: confirm leave page
|
||||||
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
||||||
|
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||||
|
|
||||||
const cameras = useMemo(() => {
|
const cameras = useMemo(() => {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@ -53,6 +64,17 @@ export default function Settings() {
|
|||||||
|
|
||||||
const [filterZoneMask, setFilterZoneMask] = useState<PolygonType[]>();
|
const [filterZoneMask, setFilterZoneMask] = useState<PolygonType[]>();
|
||||||
|
|
||||||
|
const handleDialog = useCallback(
|
||||||
|
(save: boolean) => {
|
||||||
|
if (unsavedChanges && save) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
setConfirmationDialogOpen(false);
|
||||||
|
setUnsavedChanges(false);
|
||||||
|
},
|
||||||
|
[unsavedChanges],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cameras.length) {
|
if (cameras.length) {
|
||||||
setSelectedCamera(cameras[0].name);
|
setSelectedCamera(cameras[0].name);
|
||||||
@ -123,6 +145,29 @@ export default function Settings() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{confirmationDialogOpen && (
|
||||||
|
<AlertDialog
|
||||||
|
open={confirmationDialogOpen}
|
||||||
|
onOpenChange={() => setConfirmationDialogOpen(false)}
|
||||||
|
>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>You have unsaved changes.</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Do you want to save your changes before continuing?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel onClick={() => handleDialog(false)}>
|
||||||
|
Cancel
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={() => handleDialog(true)}>
|
||||||
|
Save
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -165,7 +210,7 @@ function CameraSelectButton({
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="h-auto pt-2 overflow-y-auto overflow-x-hidden">
|
<div className="h-auto pt-2 p-3 mb-5 md:p-1 md:mb-1 overflow-y-auto overflow-x-hidden">
|
||||||
<div className="flex flex-col gap-2.5">
|
<div className="flex flex-col gap-2.5">
|
||||||
{allCameras.map((item) => (
|
{allCameras.map((item) => (
|
||||||
<FilterSwitch
|
<FilterSwitch
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user