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:
Nicolas Mowen 2024-05-29 08:01:39 -06:00 committed by GitHub
parent d5f6decd30
commit 6dd9660ecd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 128 additions and 85 deletions

View File

@ -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 && (

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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)}
/> />
); );

View File

@ -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");

View File

@ -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>

View File

@ -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[]>([]);

View File

@ -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) {

View File

@ -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 = [