mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Settings rework (#11613)
* refactor settings to be consistent with other page structure * Implement non auto live * Adjust missing view * Quick fix * Clarify settings options Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update naming and config restarts * Rename --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
parent
d5f6decd30
commit
6dd9660ecd
@ -17,6 +17,7 @@ import { cn } from "@/lib/utils";
|
|||||||
|
|
||||||
type LivePlayerProps = {
|
type LivePlayerProps = {
|
||||||
cameraRef?: (ref: HTMLDivElement | null) => void;
|
cameraRef?: (ref: HTMLDivElement | null) => void;
|
||||||
|
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||||
className?: string;
|
className?: string;
|
||||||
cameraConfig: CameraConfig;
|
cameraConfig: CameraConfig;
|
||||||
preferredLiveMode?: LivePlayerMode;
|
preferredLiveMode?: LivePlayerMode;
|
||||||
@ -26,13 +27,14 @@ type LivePlayerProps = {
|
|||||||
micEnabled?: boolean; // only webrtc supports mic
|
micEnabled?: boolean; // only webrtc supports mic
|
||||||
iOSCompatFullScreen?: boolean;
|
iOSCompatFullScreen?: boolean;
|
||||||
pip?: boolean;
|
pip?: boolean;
|
||||||
|
autoLive?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||||
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LivePlayer({
|
export default function LivePlayer({
|
||||||
cameraRef = undefined,
|
cameraRef = undefined,
|
||||||
|
containerRef,
|
||||||
className,
|
className,
|
||||||
cameraConfig,
|
cameraConfig,
|
||||||
preferredLiveMode,
|
preferredLiveMode,
|
||||||
@ -42,9 +44,9 @@ export default function LivePlayer({
|
|||||||
micEnabled = false,
|
micEnabled = false,
|
||||||
iOSCompatFullScreen = false,
|
iOSCompatFullScreen = false,
|
||||||
pip,
|
pip,
|
||||||
|
autoLive = true,
|
||||||
onClick,
|
onClick,
|
||||||
setFullResolution,
|
setFullResolution,
|
||||||
containerRef,
|
|
||||||
}: LivePlayerProps) {
|
}: LivePlayerProps) {
|
||||||
// camera activity
|
// camera activity
|
||||||
|
|
||||||
@ -64,6 +66,10 @@ export default function LivePlayer({
|
|||||||
|
|
||||||
const [liveReady, setLiveReady] = useState(false);
|
const [liveReady, setLiveReady] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!autoLive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!liveReady) {
|
if (!liveReady) {
|
||||||
if (cameraActive && liveMode == "jsmpeg") {
|
if (cameraActive && liveMode == "jsmpeg") {
|
||||||
setLiveReady(true);
|
setLiveReady(true);
|
||||||
@ -77,7 +83,7 @@ export default function LivePlayer({
|
|||||||
}
|
}
|
||||||
// live mode won't change
|
// live mode won't change
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [cameraActive, liveReady]);
|
}, [autoLive, cameraActive, liveReady]);
|
||||||
|
|
||||||
// camera still state
|
// camera still state
|
||||||
|
|
||||||
@ -91,18 +97,31 @@ export default function LivePlayer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (activeMotion || activeTracking) {
|
if (activeMotion || activeTracking) {
|
||||||
|
if (autoLive) {
|
||||||
return 200;
|
return 200;
|
||||||
|
} else {
|
||||||
|
return 59000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 30000;
|
return 30000;
|
||||||
}, [liveReady, activeMotion, activeTracking, offline, windowVisible]);
|
}, [
|
||||||
|
autoLive,
|
||||||
|
liveReady,
|
||||||
|
activeMotion,
|
||||||
|
activeTracking,
|
||||||
|
offline,
|
||||||
|
windowVisible,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!cameraConfig) {
|
if (!cameraConfig) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let player;
|
let player;
|
||||||
if (liveMode == "webrtc") {
|
if (!autoLive) {
|
||||||
|
player = null;
|
||||||
|
} else if (liveMode == "webrtc") {
|
||||||
player = (
|
player = (
|
||||||
<WebRtcPlayer
|
<WebRtcPlayer
|
||||||
className={`size-full rounded-lg md:rounded-2xl ${liveReady ? "" : "hidden"}`}
|
className={`size-full rounded-lg md:rounded-2xl ${liveReady ? "" : "hidden"}`}
|
||||||
@ -230,7 +249,7 @@ export default function LivePlayer({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute right-2 top-2">
|
<div className="absolute right-2 top-2">
|
||||||
{!offline && activeMotion && (
|
{autoLive && !offline && activeMotion && (
|
||||||
<MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
|
<MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
|
||||||
)}
|
)}
|
||||||
{offline && (
|
{offline && (
|
||||||
|
@ -131,7 +131,7 @@ export default function MotionMaskEditPane({
|
|||||||
|
|
||||||
axios
|
axios
|
||||||
.put(`config/set?${queryString}`, {
|
.put(`config/set?${queryString}`, {
|
||||||
requires_restart: 0,
|
requires_restart: 1,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
@ -189,7 +189,7 @@ export default function ObjectMaskEditPane({
|
|||||||
|
|
||||||
axios
|
axios
|
||||||
.put(`config/set?${queryString}`, {
|
.put(`config/set?${queryString}`, {
|
||||||
requires_restart: 0,
|
requires_restart: 1,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
@ -197,7 +197,7 @@ export default function ZoneEditPane({
|
|||||||
await axios.put(
|
await axios.put(
|
||||||
`config/set?cameras.${polygon.camera}.zones.${polygon.name}${renameAlertQueries}${renameDetectionQueries}`,
|
`config/set?cameras.${polygon.camera}.zones.${polygon.name}${renameAlertQueries}${renameDetectionQueries}`,
|
||||||
{
|
{
|
||||||
requires_restart: 0,
|
requires_restart: 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ export default function ZoneEditPane({
|
|||||||
axios
|
axios
|
||||||
.put(
|
.put(
|
||||||
`config/set?cameras.${polygon?.camera}.zones.${zoneName}.coordinates=${coordinates}${inertiaQuery}${loiteringTimeQuery}${objectQueries}${alertQueries}${detectionQueries}`,
|
`config/set?cameras.${polygon?.camera}.zones.${zoneName}.coordinates=${coordinates}${inertiaQuery}${loiteringTimeQuery}${objectQueries}${alertQueries}${detectionQueries}`,
|
||||||
{ requires_restart: 0 },
|
{ requires_restart: 1 },
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
@ -17,8 +17,6 @@ import {
|
|||||||
} from "@/components/ui/alert-dialog";
|
} 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 MasksAndZones from "@/components/settings/MasksAndZones";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import useOptimisticState from "@/hooks/use-optimistic-state";
|
import useOptimisticState from "@/hooks/use-optimistic-state";
|
||||||
@ -26,14 +24,16 @@ import { isMobile } from "react-device-detect";
|
|||||||
import { FaVideo } from "react-icons/fa";
|
import { FaVideo } from "react-icons/fa";
|
||||||
import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
|
import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import General from "@/components/settings/General";
|
|
||||||
import FilterSwitch from "@/components/filter/FilterSwitch";
|
import FilterSwitch from "@/components/filter/FilterSwitch";
|
||||||
import { ZoneMaskFilterButton } from "@/components/filter/ZoneMaskFilter";
|
import { ZoneMaskFilterButton } from "@/components/filter/ZoneMaskFilter";
|
||||||
import { PolygonType } from "@/types/canvas";
|
import { PolygonType } from "@/types/canvas";
|
||||||
import ObjectSettings from "@/components/settings/ObjectSettings";
|
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
import scrollIntoView from "scroll-into-view-if-needed";
|
import scrollIntoView from "scroll-into-view-if-needed";
|
||||||
import Authentication from "@/components/settings/Authentication";
|
import GeneralSettingsView from "@/views/settings/GeneralSettingsView";
|
||||||
|
import ObjectSettingsView from "@/views/settings/ObjectSettingsView";
|
||||||
|
import MotionTunerView from "@/views/settings/MotionTunerView";
|
||||||
|
import MasksAndZonesView from "@/views/settings/MasksAndZonesView";
|
||||||
|
import AuthenticationView from "@/views/settings/AuthenticationView";
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const settingsViews = [
|
const settingsViews = [
|
||||||
@ -156,22 +156,24 @@ export default function Settings() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex h-full w-full flex-col items-start md:h-dvh md:pb-24">
|
<div className="mt-2 flex h-full w-full flex-col items-start md:h-dvh md:pb-24">
|
||||||
{page == "general" && <General />}
|
{page == "general" && <GeneralSettingsView />}
|
||||||
{page == "debug" && <ObjectSettings selectedCamera={selectedCamera} />}
|
{page == "debug" && (
|
||||||
|
<ObjectSettingsView selectedCamera={selectedCamera} />
|
||||||
|
)}
|
||||||
{page == "masks / zones" && (
|
{page == "masks / zones" && (
|
||||||
<MasksAndZones
|
<MasksAndZonesView
|
||||||
selectedCamera={selectedCamera}
|
selectedCamera={selectedCamera}
|
||||||
selectedZoneMask={filterZoneMask}
|
selectedZoneMask={filterZoneMask}
|
||||||
setUnsavedChanges={setUnsavedChanges}
|
setUnsavedChanges={setUnsavedChanges}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{page == "motion tuner" && (
|
{page == "motion tuner" && (
|
||||||
<MotionTuner
|
<MotionTunerView
|
||||||
selectedCamera={selectedCamera}
|
selectedCamera={selectedCamera}
|
||||||
setUnsavedChanges={setUnsavedChanges}
|
setUnsavedChanges={setUnsavedChanges}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{page == "users" && <Authentication />}
|
{page == "users" && <AuthenticationView />}
|
||||||
</div>
|
</div>
|
||||||
{confirmationDialogOpen && (
|
{confirmationDialogOpen && (
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
|
@ -50,6 +50,7 @@ export default function LiveDashboardView({
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// recent events
|
// recent events
|
||||||
|
|
||||||
const { payload: eventUpdate } = useFrigateReviews();
|
const { payload: eventUpdate } = useFrigateReviews();
|
||||||
const { data: allEvents, mutate: updateEvents } = useSWR<ReviewSegment[]>([
|
const { data: allEvents, mutate: updateEvents } = useSWR<ReviewSegment[]>([
|
||||||
"review",
|
"review",
|
||||||
@ -92,6 +93,7 @@ export default function LiveDashboardView({
|
|||||||
|
|
||||||
// camera live views
|
// camera live views
|
||||||
|
|
||||||
|
const [autoLiveView] = usePersistence("autoLiveView", true);
|
||||||
const [windowVisible, setWindowVisible] = useState(true);
|
const [windowVisible, setWindowVisible] = useState(true);
|
||||||
const visibilityListener = useCallback(() => {
|
const visibilityListener = useCallback(() => {
|
||||||
setWindowVisible(document.visibilityState == "visible");
|
setWindowVisible(document.visibilityState == "visible");
|
||||||
@ -261,6 +263,7 @@ export default function LiveDashboardView({
|
|||||||
}
|
}
|
||||||
cameraConfig={camera}
|
cameraConfig={camera}
|
||||||
preferredLiveMode={isSafari ? "webrtc" : "mse"}
|
preferredLiveMode={isSafari ? "webrtc" : "mse"}
|
||||||
|
autoLive={autoLiveView}
|
||||||
onClick={() => onSelectCamera(camera.name)}
|
onClick={() => onSelectCamera(camera.name)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -3,20 +3,20 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
|
|||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import Heading from "../ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
import { User } from "@/types/user";
|
import { User } from "@/types/user";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import SetPasswordDialog from "../overlay/SetPasswordDialog";
|
import SetPasswordDialog from "@/components/overlay/SetPasswordDialog";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import CreateUserDialog from "../overlay/CreateUserDialog";
|
import CreateUserDialog from "@/components/overlay/CreateUserDialog";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import DeleteUserDialog from "../overlay/DeleteUserDialog";
|
import DeleteUserDialog from "@/components/overlay/DeleteUserDialog";
|
||||||
import { Card } from "../ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { HiTrash } from "react-icons/hi";
|
import { HiTrash } from "react-icons/hi";
|
||||||
import { FaUserEdit } from "react-icons/fa";
|
import { FaUserEdit } from "react-icons/fa";
|
||||||
import { LuPlus } from "react-icons/lu";
|
import { LuPlus } from "react-icons/lu";
|
||||||
|
|
||||||
export default function Authentication() {
|
export default function AuthenticationView() {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
const { data: users, mutate: mutateUsers } = useSWR<User[]>("users");
|
const { data: users, mutate: mutateUsers } = useSWR<User[]>("users");
|
||||||
|
|
@ -4,8 +4,8 @@ import { Switch } from "@/components/ui/switch";
|
|||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../../components/ui/separator";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { del as delData } from "idb-keyval";
|
import { del as delData } from "idb-keyval";
|
||||||
@ -17,11 +17,11 @@ import {
|
|||||||
SelectGroup,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "../ui/select";
|
} from "../../components/ui/select";
|
||||||
|
|
||||||
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
|
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
|
||||||
|
|
||||||
export default function General() {
|
export default function GeneralSettingsView() {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
const clearStoredLayouts = useCallback(() => {
|
const clearStoredLayouts = useCallback(() => {
|
||||||
@ -49,6 +49,9 @@ export default function General() {
|
|||||||
document.title = "General Settings - Frigate";
|
document.title = "General Settings - Frigate";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// settings
|
||||||
|
|
||||||
|
const [autoLive, setAutoLive] = usePersistence("autoLiveView", true);
|
||||||
const [playbackRate, setPlaybackRate] = usePersistence("playbackRate", 1);
|
const [playbackRate, setPlaybackRate] = usePersistence("playbackRate", 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -60,7 +63,35 @@ export default function General() {
|
|||||||
General Settings
|
General Settings
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<div className="flex w-full flex-col space-y-6">
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
|
|
||||||
|
<Heading as="h4" className="my-2">
|
||||||
|
Live Dashboard
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<div className="mt-2 space-y-6">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex flex-row items-center justify-start gap-2">
|
||||||
|
<Switch
|
||||||
|
id="auto-live"
|
||||||
|
checked={autoLive}
|
||||||
|
onCheckedChange={setAutoLive}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer" htmlFor="auto-live">
|
||||||
|
Automatic Live View
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="my-2 text-sm text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
Automatically switch to a camera's live view when activity is
|
||||||
|
detected. Disabling this option causes static camera images on
|
||||||
|
the Live dashboard to only update once per minute.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="my-3 flex w-full flex-col space-y-6">
|
||||||
<div className="mt-2 space-y-6">
|
<div className="mt-2 space-y-6">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<div className="text-md">Stored Layouts</div>
|
<div className="text-md">Stored Layouts</div>
|
||||||
@ -72,11 +103,15 @@ export default function General() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center justify-start gap-2">
|
|
||||||
<Button onClick={clearStoredLayouts}>Clear All Layouts</Button>
|
<Button onClick={clearStoredLayouts}>Clear All Layouts</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
|
|
||||||
|
<Heading as="h4" className="my-2">
|
||||||
|
Recordings Viewer
|
||||||
|
</Heading>
|
||||||
|
|
||||||
<div className="mt-2 space-y-6">
|
<div className="mt-2 space-y-6">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<div className="text-md">Default Playback Rate</div>
|
<div className="text-md">Default Playback Rate</div>
|
||||||
@ -107,26 +142,6 @@ export default function General() {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
<div className="mt-2 space-y-6">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<div className="text-md">Low Data Mode</div>
|
|
||||||
<div className="my-2 text-sm text-muted-foreground">
|
|
||||||
<p>
|
|
||||||
Not yet implemented. <em>Default: disabled</em>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row items-center justify-start gap-2">
|
|
||||||
<Switch
|
|
||||||
id="lowdata"
|
|
||||||
checked={false}
|
|
||||||
onCheckedChange={() => {}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="lowdata">
|
|
||||||
Low Data Mode (this device only)
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -9,10 +9,10 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { PolygonCanvas } from "./PolygonCanvas";
|
import { PolygonCanvas } from "@/components/settings/PolygonCanvas";
|
||||||
import { Polygon, PolygonType } from "@/types/canvas";
|
import { Polygon, PolygonType } from "@/types/canvas";
|
||||||
import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil";
|
import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||||
import { LuExternalLink, LuPlus } from "react-icons/lu";
|
import { LuExternalLink, LuPlus } from "react-icons/lu";
|
||||||
import {
|
import {
|
||||||
@ -22,29 +22,33 @@ import {
|
|||||||
} from "@/components/ui/hover-card";
|
} from "@/components/ui/hover-card";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Toaster } from "../ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
import {
|
||||||
import Heading from "../ui/heading";
|
Tooltip,
|
||||||
import ZoneEditPane from "./ZoneEditPane";
|
TooltipContent,
|
||||||
import MotionMaskEditPane from "./MotionMaskEditPane";
|
TooltipTrigger,
|
||||||
import ObjectMaskEditPane from "./ObjectMaskEditPane";
|
} from "@/components/ui/tooltip";
|
||||||
import PolygonItem from "./PolygonItem";
|
import Heading from "@/components/ui/heading";
|
||||||
|
import ZoneEditPane from "@/components/settings/ZoneEditPane";
|
||||||
|
import MotionMaskEditPane from "@/components/settings/MotionMaskEditPane";
|
||||||
|
import ObjectMaskEditPane from "@/components/settings/ObjectMaskEditPane";
|
||||||
|
import PolygonItem from "@/components/settings/PolygonItem";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { isDesktop } from "react-device-detect";
|
import { isDesktop } from "react-device-detect";
|
||||||
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
|
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
|
||||||
|
|
||||||
type MasksAndZoneProps = {
|
type MasksAndZoneViewProps = {
|
||||||
selectedCamera: string;
|
selectedCamera: string;
|
||||||
selectedZoneMask?: PolygonType[];
|
selectedZoneMask?: PolygonType[];
|
||||||
setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
|
setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MasksAndZones({
|
export default function MasksAndZonesView({
|
||||||
selectedCamera,
|
selectedCamera,
|
||||||
selectedZoneMask,
|
selectedZoneMask,
|
||||||
setUnsavedChanges,
|
setUnsavedChanges,
|
||||||
}: MasksAndZoneProps) {
|
}: MasksAndZoneViewProps) {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
|
const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
|
||||||
const [editingPolygons, setEditingPolygons] = useState<Polygon[]>([]);
|
const [editingPolygons, setEditingPolygons] = useState<Polygon[]>([]);
|
@ -12,17 +12,17 @@ import {
|
|||||||
useMotionContourArea,
|
useMotionContourArea,
|
||||||
useMotionThreshold,
|
useMotionThreshold,
|
||||||
} from "@/api/ws";
|
} from "@/api/ws";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Switch } from "../ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { LuExternalLink } from "react-icons/lu";
|
import { LuExternalLink } from "react-icons/lu";
|
||||||
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
|
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
|
||||||
|
|
||||||
type MotionTunerProps = {
|
type MotionTunerViewProps = {
|
||||||
selectedCamera: string;
|
selectedCamera: string;
|
||||||
setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
|
setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
};
|
};
|
||||||
@ -33,10 +33,10 @@ type MotionSettings = {
|
|||||||
improve_contrast?: boolean;
|
improve_contrast?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MotionTuner({
|
export default function MotionTunerView({
|
||||||
selectedCamera,
|
selectedCamera,
|
||||||
setUnsavedChanges,
|
setUnsavedChanges,
|
||||||
}: MotionTunerProps) {
|
}: MotionTunerViewProps) {
|
||||||
const { data: config, mutate: updateConfig } =
|
const { data: config, mutate: updateConfig } =
|
||||||
useSWR<FrigateConfig>("config");
|
useSWR<FrigateConfig>("config");
|
||||||
const [changedValue, setChangedValue] = useState(false);
|
const [changedValue, setChangedValue] = useState(false);
|
||||||
@ -113,7 +113,7 @@ export default function MotionTuner({
|
|||||||
axios
|
axios
|
||||||
.put(
|
.put(
|
||||||
`config/set?cameras.${selectedCamera}.motion.threshold=${motionSettings.threshold}&cameras.${selectedCamera}.motion.contour_area=${motionSettings.contour_area}&cameras.${selectedCamera}.motion.improve_contrast=${motionSettings.improve_contrast}`,
|
`config/set?cameras.${selectedCamera}.motion.threshold=${motionSettings.threshold}&cameras.${selectedCamera}.motion.contour_area=${motionSettings.contour_area}&cameras.${selectedCamera}.motion.improve_contrast=${motionSettings.improve_contrast}`,
|
||||||
{ requires_restart: 0 },
|
{ requires_restart: 1 },
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
@ -5,19 +5,19 @@ import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
|
|||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import Heading from "../ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
import { Switch } from "../ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { usePersistence } from "@/hooks/use-persistence";
|
import { usePersistence } from "@/hooks/use-persistence";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { useCameraActivity } from "@/hooks/use-camera-activity";
|
import { useCameraActivity } from "@/hooks/use-camera-activity";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { ObjectType } from "@/types/ws";
|
import { ObjectType } from "@/types/ws";
|
||||||
import useDeepMemo from "@/hooks/use-deep-memo";
|
import useDeepMemo from "@/hooks/use-deep-memo";
|
||||||
import { Card } from "../ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { getIconForLabel } from "@/utils/iconUtil";
|
import { getIconForLabel } from "@/utils/iconUtil";
|
||||||
import { capitalizeFirstLetter } from "@/utils/stringUtil";
|
import { capitalizeFirstLetter } from "@/utils/stringUtil";
|
||||||
|
|
||||||
type ObjectSettingsProps = {
|
type ObjectSettingsViewProps = {
|
||||||
selectedCamera?: string;
|
selectedCamera?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,9 +25,9 @@ type Options = { [key: string]: boolean };
|
|||||||
|
|
||||||
const emptyObject = Object.freeze({});
|
const emptyObject = Object.freeze({});
|
||||||
|
|
||||||
export default function ObjectSettings({
|
export default function ObjectSettingsView({
|
||||||
selectedCamera,
|
selectedCamera,
|
||||||
}: ObjectSettingsProps) {
|
}: ObjectSettingsViewProps) {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
const DEBUG_OPTIONS = [
|
const DEBUG_OPTIONS = [
|
Loading…
Reference in New Issue
Block a user