mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +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 = {
 | 
			
		||||
  cameraRef?: (ref: HTMLDivElement | null) => void;
 | 
			
		||||
  containerRef?: React.MutableRefObject<HTMLDivElement | null>;
 | 
			
		||||
  className?: string;
 | 
			
		||||
  cameraConfig: CameraConfig;
 | 
			
		||||
  preferredLiveMode?: LivePlayerMode;
 | 
			
		||||
@ -26,13 +27,14 @@ type LivePlayerProps = {
 | 
			
		||||
  micEnabled?: boolean; // only webrtc supports mic
 | 
			
		||||
  iOSCompatFullScreen?: boolean;
 | 
			
		||||
  pip?: boolean;
 | 
			
		||||
  autoLive?: boolean;
 | 
			
		||||
  onClick?: () => void;
 | 
			
		||||
  setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
 | 
			
		||||
  containerRef?: React.MutableRefObject<HTMLDivElement | null>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function LivePlayer({
 | 
			
		||||
  cameraRef = undefined,
 | 
			
		||||
  containerRef,
 | 
			
		||||
  className,
 | 
			
		||||
  cameraConfig,
 | 
			
		||||
  preferredLiveMode,
 | 
			
		||||
@ -42,9 +44,9 @@ export default function LivePlayer({
 | 
			
		||||
  micEnabled = false,
 | 
			
		||||
  iOSCompatFullScreen = false,
 | 
			
		||||
  pip,
 | 
			
		||||
  autoLive = true,
 | 
			
		||||
  onClick,
 | 
			
		||||
  setFullResolution,
 | 
			
		||||
  containerRef,
 | 
			
		||||
}: LivePlayerProps) {
 | 
			
		||||
  // camera activity
 | 
			
		||||
 | 
			
		||||
@ -64,6 +66,10 @@ export default function LivePlayer({
 | 
			
		||||
 | 
			
		||||
  const [liveReady, setLiveReady] = useState(false);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!autoLive) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!liveReady) {
 | 
			
		||||
      if (cameraActive && liveMode == "jsmpeg") {
 | 
			
		||||
        setLiveReady(true);
 | 
			
		||||
@ -77,7 +83,7 @@ export default function LivePlayer({
 | 
			
		||||
    }
 | 
			
		||||
    // live mode won't change
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, [cameraActive, liveReady]);
 | 
			
		||||
  }, [autoLive, cameraActive, liveReady]);
 | 
			
		||||
 | 
			
		||||
  // camera still state
 | 
			
		||||
 | 
			
		||||
@ -91,18 +97,31 @@ export default function LivePlayer({
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (activeMotion || activeTracking) {
 | 
			
		||||
      if (autoLive) {
 | 
			
		||||
        return 200;
 | 
			
		||||
      } else {
 | 
			
		||||
        return 59000;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 30000;
 | 
			
		||||
  }, [liveReady, activeMotion, activeTracking, offline, windowVisible]);
 | 
			
		||||
  }, [
 | 
			
		||||
    autoLive,
 | 
			
		||||
    liveReady,
 | 
			
		||||
    activeMotion,
 | 
			
		||||
    activeTracking,
 | 
			
		||||
    offline,
 | 
			
		||||
    windowVisible,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  if (!cameraConfig) {
 | 
			
		||||
    return <ActivityIndicator />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let player;
 | 
			
		||||
  if (liveMode == "webrtc") {
 | 
			
		||||
  if (!autoLive) {
 | 
			
		||||
    player = null;
 | 
			
		||||
  } else if (liveMode == "webrtc") {
 | 
			
		||||
    player = (
 | 
			
		||||
      <WebRtcPlayer
 | 
			
		||||
        className={`size-full rounded-lg md:rounded-2xl ${liveReady ? "" : "hidden"}`}
 | 
			
		||||
@ -230,7 +249,7 @@ export default function LivePlayer({
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <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" />
 | 
			
		||||
        )}
 | 
			
		||||
        {offline && (
 | 
			
		||||
 | 
			
		||||
@ -131,7 +131,7 @@ export default function MotionMaskEditPane({
 | 
			
		||||
 | 
			
		||||
    axios
 | 
			
		||||
      .put(`config/set?${queryString}`, {
 | 
			
		||||
        requires_restart: 0,
 | 
			
		||||
        requires_restart: 1,
 | 
			
		||||
      })
 | 
			
		||||
      .then((res) => {
 | 
			
		||||
        if (res.status === 200) {
 | 
			
		||||
 | 
			
		||||
@ -189,7 +189,7 @@ export default function ObjectMaskEditPane({
 | 
			
		||||
 | 
			
		||||
      axios
 | 
			
		||||
        .put(`config/set?${queryString}`, {
 | 
			
		||||
          requires_restart: 0,
 | 
			
		||||
          requires_restart: 1,
 | 
			
		||||
        })
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          if (res.status === 200) {
 | 
			
		||||
 | 
			
		||||
@ -197,7 +197,7 @@ export default function ZoneEditPane({
 | 
			
		||||
          await axios.put(
 | 
			
		||||
            `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
 | 
			
		||||
        .put(
 | 
			
		||||
          `config/set?cameras.${polygon?.camera}.zones.${zoneName}.coordinates=${coordinates}${inertiaQuery}${loiteringTimeQuery}${objectQueries}${alertQueries}${detectionQueries}`,
 | 
			
		||||
          { requires_restart: 0 },
 | 
			
		||||
          { requires_restart: 1 },
 | 
			
		||||
        )
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          if (res.status === 200) {
 | 
			
		||||
 | 
			
		||||
@ -17,8 +17,6 @@ import {
 | 
			
		||||
} from "@/components/ui/alert-dialog";
 | 
			
		||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
 | 
			
		||||
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 { useCallback, useEffect, useMemo, useRef, useState } from "react";
 | 
			
		||||
import useOptimisticState from "@/hooks/use-optimistic-state";
 | 
			
		||||
@ -26,14 +24,16 @@ import { isMobile } from "react-device-detect";
 | 
			
		||||
import { FaVideo } from "react-icons/fa";
 | 
			
		||||
import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import General from "@/components/settings/General";
 | 
			
		||||
import FilterSwitch from "@/components/filter/FilterSwitch";
 | 
			
		||||
import { ZoneMaskFilterButton } from "@/components/filter/ZoneMaskFilter";
 | 
			
		||||
import { PolygonType } from "@/types/canvas";
 | 
			
		||||
import ObjectSettings from "@/components/settings/ObjectSettings";
 | 
			
		||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
 | 
			
		||||
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() {
 | 
			
		||||
  const settingsViews = [
 | 
			
		||||
@ -156,22 +156,24 @@ export default function Settings() {
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="mt-2 flex h-full w-full flex-col items-start md:h-dvh md:pb-24">
 | 
			
		||||
        {page == "general" && <General />}
 | 
			
		||||
        {page == "debug" && <ObjectSettings selectedCamera={selectedCamera} />}
 | 
			
		||||
        {page == "general" && <GeneralSettingsView />}
 | 
			
		||||
        {page == "debug" && (
 | 
			
		||||
          <ObjectSettingsView selectedCamera={selectedCamera} />
 | 
			
		||||
        )}
 | 
			
		||||
        {page == "masks / zones" && (
 | 
			
		||||
          <MasksAndZones
 | 
			
		||||
          <MasksAndZonesView
 | 
			
		||||
            selectedCamera={selectedCamera}
 | 
			
		||||
            selectedZoneMask={filterZoneMask}
 | 
			
		||||
            setUnsavedChanges={setUnsavedChanges}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
        {page == "motion tuner" && (
 | 
			
		||||
          <MotionTuner
 | 
			
		||||
          <MotionTunerView
 | 
			
		||||
            selectedCamera={selectedCamera}
 | 
			
		||||
            setUnsavedChanges={setUnsavedChanges}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
        {page == "users" && <Authentication />}
 | 
			
		||||
        {page == "users" && <AuthenticationView />}
 | 
			
		||||
      </div>
 | 
			
		||||
      {confirmationDialogOpen && (
 | 
			
		||||
        <AlertDialog
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,7 @@ export default function LiveDashboardView({
 | 
			
		||||
  const containerRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
 | 
			
		||||
  // recent events
 | 
			
		||||
 | 
			
		||||
  const { payload: eventUpdate } = useFrigateReviews();
 | 
			
		||||
  const { data: allEvents, mutate: updateEvents } = useSWR<ReviewSegment[]>([
 | 
			
		||||
    "review",
 | 
			
		||||
@ -92,6 +93,7 @@ export default function LiveDashboardView({
 | 
			
		||||
 | 
			
		||||
  // camera live views
 | 
			
		||||
 | 
			
		||||
  const [autoLiveView] = usePersistence("autoLiveView", true);
 | 
			
		||||
  const [windowVisible, setWindowVisible] = useState(true);
 | 
			
		||||
  const visibilityListener = useCallback(() => {
 | 
			
		||||
    setWindowVisible(document.visibilityState == "visible");
 | 
			
		||||
@ -261,6 +263,7 @@ export default function LiveDashboardView({
 | 
			
		||||
                }
 | 
			
		||||
                cameraConfig={camera}
 | 
			
		||||
                preferredLiveMode={isSafari ? "webrtc" : "mse"}
 | 
			
		||||
                autoLive={autoLiveView}
 | 
			
		||||
                onClick={() => onSelectCamera(camera.name)}
 | 
			
		||||
              />
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
@ -3,20 +3,20 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
 | 
			
		||||
import { FrigateConfig } from "@/types/frigateConfig";
 | 
			
		||||
import { Toaster } from "@/components/ui/sonner";
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import Heading from "../ui/heading";
 | 
			
		||||
import Heading from "@/components/ui/heading";
 | 
			
		||||
import { User } from "@/types/user";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
import SetPasswordDialog from "../overlay/SetPasswordDialog";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import SetPasswordDialog from "@/components/overlay/SetPasswordDialog";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
import CreateUserDialog from "../overlay/CreateUserDialog";
 | 
			
		||||
import CreateUserDialog from "@/components/overlay/CreateUserDialog";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import DeleteUserDialog from "../overlay/DeleteUserDialog";
 | 
			
		||||
import { Card } from "../ui/card";
 | 
			
		||||
import DeleteUserDialog from "@/components/overlay/DeleteUserDialog";
 | 
			
		||||
import { Card } from "@/components/ui/card";
 | 
			
		||||
import { HiTrash } from "react-icons/hi";
 | 
			
		||||
import { FaUserEdit } from "react-icons/fa";
 | 
			
		||||
import { LuPlus } from "react-icons/lu";
 | 
			
		||||
 | 
			
		||||
export default function Authentication() {
 | 
			
		||||
export default function AuthenticationView() {
 | 
			
		||||
  const { data: config } = useSWR<FrigateConfig>("config");
 | 
			
		||||
  const { data: users, mutate: mutateUsers } = useSWR<User[]>("users");
 | 
			
		||||
 | 
			
		||||
@ -4,8 +4,8 @@ import { Switch } from "@/components/ui/switch";
 | 
			
		||||
import { useCallback, useEffect } from "react";
 | 
			
		||||
import { Toaster } from "sonner";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import { Separator } from "../ui/separator";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
import { Separator } from "../../components/ui/separator";
 | 
			
		||||
import { Button } from "../../components/ui/button";
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import { FrigateConfig } from "@/types/frigateConfig";
 | 
			
		||||
import { del as delData } from "idb-keyval";
 | 
			
		||||
@ -17,11 +17,11 @@ import {
 | 
			
		||||
  SelectGroup,
 | 
			
		||||
  SelectItem,
 | 
			
		||||
  SelectTrigger,
 | 
			
		||||
} from "../ui/select";
 | 
			
		||||
} from "../../components/ui/select";
 | 
			
		||||
 | 
			
		||||
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 clearStoredLayouts = useCallback(() => {
 | 
			
		||||
@ -49,6 +49,9 @@ export default function General() {
 | 
			
		||||
    document.title = "General Settings - Frigate";
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  // settings
 | 
			
		||||
 | 
			
		||||
  const [autoLive, setAutoLive] = usePersistence("autoLiveView", true);
 | 
			
		||||
  const [playbackRate, setPlaybackRate] = usePersistence("playbackRate", 1);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@ -60,7 +63,35 @@ export default function General() {
 | 
			
		||||
            General Settings
 | 
			
		||||
          </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="space-y-0.5">
 | 
			
		||||
                <div className="text-md">Stored Layouts</div>
 | 
			
		||||
@ -72,11 +103,15 @@ export default function General() {
 | 
			
		||||
                  </p>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="flex flex-row items-center justify-start gap-2">
 | 
			
		||||
              <Button onClick={clearStoredLayouts}>Clear All Layouts</Button>
 | 
			
		||||
            </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <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="space-y-0.5">
 | 
			
		||||
                <div className="text-md">Default Playback Rate</div>
 | 
			
		||||
@ -107,26 +142,6 @@ export default function General() {
 | 
			
		||||
              </SelectContent>
 | 
			
		||||
            </Select>
 | 
			
		||||
            <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>
 | 
			
		||||
@ -9,10 +9,10 @@ import {
 | 
			
		||||
  useRef,
 | 
			
		||||
  useState,
 | 
			
		||||
} from "react";
 | 
			
		||||
import { PolygonCanvas } from "./PolygonCanvas";
 | 
			
		||||
import { PolygonCanvas } from "@/components/settings/PolygonCanvas";
 | 
			
		||||
import { Polygon, PolygonType } from "@/types/canvas";
 | 
			
		||||
import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil";
 | 
			
		||||
import { Skeleton } from "../ui/skeleton";
 | 
			
		||||
import { Skeleton } from "@/components/ui/skeleton";
 | 
			
		||||
import { useResizeObserver } from "@/hooks/resize-observer";
 | 
			
		||||
import { LuExternalLink, LuPlus } from "react-icons/lu";
 | 
			
		||||
import {
 | 
			
		||||
@ -22,29 +22,33 @@ import {
 | 
			
		||||
} from "@/components/ui/hover-card";
 | 
			
		||||
import copy from "copy-to-clipboard";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import { Toaster } from "../ui/sonner";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
 | 
			
		||||
import Heading from "../ui/heading";
 | 
			
		||||
import ZoneEditPane from "./ZoneEditPane";
 | 
			
		||||
import MotionMaskEditPane from "./MotionMaskEditPane";
 | 
			
		||||
import ObjectMaskEditPane from "./ObjectMaskEditPane";
 | 
			
		||||
import PolygonItem from "./PolygonItem";
 | 
			
		||||
import { Toaster } from "@/components/ui/sonner";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import {
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  TooltipContent,
 | 
			
		||||
  TooltipTrigger,
 | 
			
		||||
} from "@/components/ui/tooltip";
 | 
			
		||||
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 { isDesktop } from "react-device-detect";
 | 
			
		||||
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
 | 
			
		||||
 | 
			
		||||
type MasksAndZoneProps = {
 | 
			
		||||
type MasksAndZoneViewProps = {
 | 
			
		||||
  selectedCamera: string;
 | 
			
		||||
  selectedZoneMask?: PolygonType[];
 | 
			
		||||
  setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function MasksAndZones({
 | 
			
		||||
export default function MasksAndZonesView({
 | 
			
		||||
  selectedCamera,
 | 
			
		||||
  selectedZoneMask,
 | 
			
		||||
  setUnsavedChanges,
 | 
			
		||||
}: MasksAndZoneProps) {
 | 
			
		||||
}: MasksAndZoneViewProps) {
 | 
			
		||||
  const { data: config } = useSWR<FrigateConfig>("config");
 | 
			
		||||
  const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
 | 
			
		||||
  const [editingPolygons, setEditingPolygons] = useState<Polygon[]>([]);
 | 
			
		||||
@ -12,17 +12,17 @@ import {
 | 
			
		||||
  useMotionContourArea,
 | 
			
		||||
  useMotionThreshold,
 | 
			
		||||
} from "@/api/ws";
 | 
			
		||||
import { Skeleton } from "../ui/skeleton";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
import { Switch } from "../ui/switch";
 | 
			
		||||
import { Skeleton } from "@/components/ui/skeleton";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Switch } from "@/components/ui/switch";
 | 
			
		||||
import { Toaster } from "@/components/ui/sonner";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import { Separator } from "../ui/separator";
 | 
			
		||||
import { Separator } from "@/components/ui/separator";
 | 
			
		||||
import { Link } from "react-router-dom";
 | 
			
		||||
import { LuExternalLink } from "react-icons/lu";
 | 
			
		||||
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
 | 
			
		||||
 | 
			
		||||
type MotionTunerProps = {
 | 
			
		||||
type MotionTunerViewProps = {
 | 
			
		||||
  selectedCamera: string;
 | 
			
		||||
  setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
 | 
			
		||||
};
 | 
			
		||||
@ -33,10 +33,10 @@ type MotionSettings = {
 | 
			
		||||
  improve_contrast?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function MotionTuner({
 | 
			
		||||
export default function MotionTunerView({
 | 
			
		||||
  selectedCamera,
 | 
			
		||||
  setUnsavedChanges,
 | 
			
		||||
}: MotionTunerProps) {
 | 
			
		||||
}: MotionTunerViewProps) {
 | 
			
		||||
  const { data: config, mutate: updateConfig } =
 | 
			
		||||
    useSWR<FrigateConfig>("config");
 | 
			
		||||
  const [changedValue, setChangedValue] = useState(false);
 | 
			
		||||
@ -113,7 +113,7 @@ export default function MotionTuner({
 | 
			
		||||
    axios
 | 
			
		||||
      .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}`,
 | 
			
		||||
        { requires_restart: 0 },
 | 
			
		||||
        { requires_restart: 1 },
 | 
			
		||||
      )
 | 
			
		||||
      .then((res) => {
 | 
			
		||||
        if (res.status === 200) {
 | 
			
		||||
@ -5,19 +5,19 @@ import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
 | 
			
		||||
import { Toaster } from "@/components/ui/sonner";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import Heading from "../ui/heading";
 | 
			
		||||
import { Switch } from "../ui/switch";
 | 
			
		||||
import Heading from "@/components/ui/heading";
 | 
			
		||||
import { Switch } from "@/components/ui/switch";
 | 
			
		||||
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 { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
 | 
			
		||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
 | 
			
		||||
import { ObjectType } from "@/types/ws";
 | 
			
		||||
import useDeepMemo from "@/hooks/use-deep-memo";
 | 
			
		||||
import { Card } from "../ui/card";
 | 
			
		||||
import { Card } from "@/components/ui/card";
 | 
			
		||||
import { getIconForLabel } from "@/utils/iconUtil";
 | 
			
		||||
import { capitalizeFirstLetter } from "@/utils/stringUtil";
 | 
			
		||||
 | 
			
		||||
type ObjectSettingsProps = {
 | 
			
		||||
type ObjectSettingsViewProps = {
 | 
			
		||||
  selectedCamera?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -25,9 +25,9 @@ type Options = { [key: string]: boolean };
 | 
			
		||||
 | 
			
		||||
const emptyObject = Object.freeze({});
 | 
			
		||||
 | 
			
		||||
export default function ObjectSettings({
 | 
			
		||||
export default function ObjectSettingsView({
 | 
			
		||||
  selectedCamera,
 | 
			
		||||
}: ObjectSettingsProps) {
 | 
			
		||||
}: ObjectSettingsViewProps) {
 | 
			
		||||
  const { data: config } = useSWR<FrigateConfig>("config");
 | 
			
		||||
 | 
			
		||||
  const DEBUG_OPTIONS = [
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user