remove unused

This commit is contained in:
Josh Hawkins 2026-02-01 13:34:04 -06:00
parent 88578284a5
commit f8eee8ed0b
2 changed files with 0 additions and 468 deletions

View File

@ -1,274 +0,0 @@
// Camera Configuration View
// Per-camera configuration with tab navigation and override indicators
import { useMemo, useCallback, useState, memo } from "react";
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { ConfigSectionTemplate } from "@/components/config-form/sections";
import { useAllCameraOverrides } from "@/hooks/use-config-override";
import type { FrigateConfig } from "@/types/frigateConfig";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Badge } from "@/components/ui/badge";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import Heading from "@/components/ui/heading";
import { cn } from "@/lib/utils";
interface CameraConfigViewProps {
/** Currently selected camera (from parent) */
selectedCamera?: string;
/** Callback when unsaved changes state changes */
setUnsavedChanges?: React.Dispatch<React.SetStateAction<boolean>>;
}
export default function CameraConfigView({
selectedCamera: externalSelectedCamera,
setUnsavedChanges,
}: CameraConfigViewProps) {
const { t } = useTranslation(["views/settings"]);
const { data: config, mutate: refreshConfig } =
useSWR<FrigateConfig>("config");
// Get list of cameras
const cameras = useMemo(() => {
if (!config?.cameras) return [];
return Object.keys(config.cameras).sort();
}, [config]);
// Selected camera state (use external if provided, else internal)
const [internalSelectedCamera, setInternalSelectedCamera] = useState<string>(
cameras[0] || "",
);
const selectedCamera = externalSelectedCamera || internalSelectedCamera;
// Get overridden sections for current camera
const overriddenSections = useAllCameraOverrides(config, selectedCamera);
const handleSave = useCallback(() => {
refreshConfig();
setUnsavedChanges?.(false);
}, [refreshConfig, setUnsavedChanges]);
const handleCameraChange = useCallback((camera: string) => {
setInternalSelectedCamera(camera);
}, []);
if (!config) {
return (
<div className="flex h-full items-center justify-center">
<ActivityIndicator />
</div>
);
}
if (cameras.length === 0) {
return (
<div className="flex h-full items-center justify-center text-muted-foreground">
{t("configForm.camera.noCameras", {
defaultValue: "No cameras configured",
})}
</div>
);
}
return (
<div className="flex size-full flex-col">
<div className="mb-4">
<Heading as="h2">
{t("configForm.camera.title", {
defaultValue: "Camera Configuration",
})}
</Heading>
<p className="text-sm text-muted-foreground">
{t("configForm.camera.description", {
defaultValue:
"Configure settings for individual cameras. Overridden settings are highlighted.",
})}
</p>
</div>
{/* Camera Tabs - Only show if not externally controlled */}
{!externalSelectedCamera && (
<Tabs
value={selectedCamera}
onValueChange={handleCameraChange}
className="flex flex-1 flex-col"
>
<ScrollArea className="w-full">
<TabsList className="inline-flex w-max">
{cameras.map((camera) => {
const cameraOverrides = overriddenSections.filter((s) =>
s.startsWith(camera),
);
const hasOverrides = cameraOverrides.length > 0;
const cameraConfig = config.cameras[camera];
const displayName = cameraConfig?.name || camera;
return (
<TabsTrigger
key={camera}
value={camera}
className="relative gap-2"
>
{displayName}
{hasOverrides && (
<Badge variant="secondary" className="ml-1 h-5 px-1.5">
{cameraOverrides.length}
</Badge>
)}
</TabsTrigger>
);
})}
</TabsList>
</ScrollArea>
{cameras.map((camera) => (
<TabsContent key={camera} value={camera} className="mt-4 flex-1">
<CameraConfigContent
cameraName={camera}
config={config}
overriddenSections={overriddenSections}
onSave={handleSave}
/>
</TabsContent>
))}
</Tabs>
)}
{/* Direct content when externally controlled */}
{externalSelectedCamera && (
<CameraConfigContent
cameraName={externalSelectedCamera}
config={config}
overriddenSections={overriddenSections}
onSave={handleSave}
/>
)}
</div>
);
}
interface CameraConfigContentProps {
cameraName: string;
config: FrigateConfig;
overriddenSections: string[];
onSave: () => void;
}
const CameraConfigContent = memo(function CameraConfigContent({
cameraName,
config,
overriddenSections,
onSave,
}: CameraConfigContentProps) {
const { t } = useTranslation([
"config/cameras",
"config/cameras",
"views/settings",
"common",
]);
const [activeSection, setActiveSection] = useState("detect");
const cameraConfig = config.cameras?.[cameraName];
if (!cameraConfig) {
return (
<div className="text-muted-foreground">
{t("configForm.camera.notFound", {
ns: "views/settings",
defaultValue: "Camera not found",
})}
</div>
);
}
const sections: Array<{
key: string;
showOverrideIndicator?: boolean;
}> = [
{ key: "detect" },
{ key: "ffmpeg", showOverrideIndicator: true },
{ key: "record" },
{ key: "snapshots" },
{ key: "motion" },
{ key: "objects" },
{ key: "review" },
{ key: "audio" },
{ key: "audio_transcription", showOverrideIndicator: true },
{ key: "notifications" },
{ key: "live" },
{ key: "birdseye", showOverrideIndicator: true },
{ key: "face_recognition", showOverrideIndicator: true },
{ key: "lpr", showOverrideIndicator: true },
{ key: "mqtt", showOverrideIndicator: false },
{ key: "onvif", showOverrideIndicator: false },
{ key: "ui", showOverrideIndicator: false },
{ key: "timestamp_style" },
];
return (
<div className="flex flex-1 gap-6 overflow-hidden">
{/* Section Navigation */}
<nav className="w-64 shrink-0">
<ul className="space-y-1">
{sections.map((section) => {
const isOverridden = overriddenSections.includes(section.key);
const defaultSectionLabel =
section.key.charAt(0).toUpperCase() +
section.key.slice(1).replace(/_/g, " ");
const sectionLabel = t(`${section.key}.label`, {
ns: "config/cameras",
defaultValue: defaultSectionLabel,
});
return (
<li key={section.key}>
<button
onClick={() => setActiveSection(section.key)}
className={cn(
"flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-sm transition-colors",
activeSection === section.key
? "bg-accent text-accent-foreground"
: "hover:bg-muted",
)}
>
<span>{sectionLabel}</span>
{isOverridden && (
<Badge variant="secondary" className="h-5 px-1.5 text-xs">
{t("button.overridden", {
ns: "common",
defaultValue: "Overridden",
})}
</Badge>
)}
</button>
</li>
);
})}
</ul>
</nav>
{/* Section Content */}
<div className="scrollbar-container flex-1 overflow-y-auto pr-4">
{sections.map((section) => (
<div
key={section.key}
className={cn(activeSection === section.key ? "block" : "hidden")}
>
<ConfigSectionTemplate
sectionKey={section.key}
level="camera"
cameraName={cameraName}
showOverrideIndicator={section.showOverrideIndicator !== false}
onSave={onSave}
showTitle={true}
/>
</div>
))}
</div>
</div>
);
});

View File

@ -1,194 +0,0 @@
// Global Configuration View
// Main view for configuring global Frigate settings
import { useMemo, useCallback, useState } from "react";
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { ConfigSectionTemplate } from "@/components/config-form/sections";
import type { FrigateConfig } from "@/types/frigateConfig";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import Heading from "@/components/ui/heading";
import { cn } from "@/lib/utils";
// Shared sections that can be overridden at camera level
const sharedSections = [
{ key: "detect" },
{ key: "record" },
{ key: "snapshots" },
{ key: "motion" },
{ key: "objects" },
{ key: "review" },
{ key: "audio" },
{ key: "live" },
{ key: "timestamp_style" },
];
// System sections (global only)
const systemSections = [
{ key: "database" },
{ key: "tls" },
{ key: "auth" },
{ key: "networking" },
{ key: "proxy" },
{ key: "ui" },
{ key: "logger" },
{ key: "environment_vars" },
{ key: "telemetry" },
{ key: "birdseye" },
{ key: "ffmpeg" },
{ key: "detectors" },
{ key: "model" },
];
// Integration sections (global only)
const integrationSections = [
{ key: "mqtt" },
{ key: "semantic_search" },
{ key: "genai" },
{ key: "face_recognition" },
{ key: "lpr" },
{ key: "classification" },
{ key: "audio_transcription" },
];
export default function GlobalConfigView() {
const { t } = useTranslation(["views/settings", "config/global", "common"]);
const defaultSharedSection = sharedSections[0]?.key ?? "";
const defaultSystemSection = systemSections[0]?.key ?? "";
const defaultIntegrationSection = integrationSections[0]?.key ?? "";
const [activeTab, setActiveTab] = useState("shared");
const [activeSection, setActiveSection] = useState(defaultSharedSection);
const { data: config, mutate: refreshConfig } =
useSWR<FrigateConfig>("config");
const handleSave = useCallback(() => {
refreshConfig();
}, [refreshConfig]);
// Get the sections for the current tab
const currentSections = useMemo(() => {
if (activeTab === "shared") {
return sharedSections;
}
if (activeTab === "system") {
return systemSections;
}
return integrationSections;
}, [activeTab]);
// Reset active section when tab changes
const handleTabChange = useCallback(
(tab: string) => {
setActiveTab(tab);
if (tab === "shared") {
setActiveSection(defaultSharedSection);
} else if (tab === "system") {
setActiveSection(defaultSystemSection);
} else {
setActiveSection(defaultIntegrationSection);
}
},
[defaultSharedSection, defaultSystemSection, defaultIntegrationSection],
);
if (!config) {
return (
<div className="flex h-full items-center justify-center">
<ActivityIndicator />
</div>
);
}
return (
<div className="flex size-full flex-col">
<div className="mb-4">
<Heading as="h2">
{t("configForm.global.title", {
defaultValue: "Global Configuration",
})}
</Heading>
<p className="text-sm text-muted-foreground">
{t("configForm.global.description", {
defaultValue:
"Configure global settings that apply to all cameras by default.",
})}
</p>
</div>
<Tabs
value={activeTab}
onValueChange={handleTabChange}
className="flex flex-1 flex-col overflow-hidden"
>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="shared">
{t("configForm.global.tabs.shared", {
defaultValue: "Shared Defaults",
})}
</TabsTrigger>
<TabsTrigger value="system">
{t("configForm.global.tabs.system", { defaultValue: "System" })}
</TabsTrigger>
<TabsTrigger value="integrations">
{t("configForm.global.tabs.integrations", {
defaultValue: "Integrations",
})}
</TabsTrigger>
</TabsList>
<div className="mt-4 flex flex-1 gap-6 overflow-hidden">
{/* Section Navigation */}
<nav className="w-64 shrink-0">
<ul className="space-y-1">
{currentSections.map((section) => {
const defaultLabel =
section.key.charAt(0).toUpperCase() +
section.key.slice(1).replace(/_/g, " ");
const sectionLabel = t(`${section.key}.label`, {
ns: "config/global",
defaultValue: defaultLabel,
});
return (
<li key={section.key}>
<button
onClick={() => setActiveSection(section.key)}
className={cn(
"flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-sm transition-colors",
activeSection === section.key
? "bg-accent text-accent-foreground"
: "hover:bg-muted",
)}
>
<span>{sectionLabel}</span>
</button>
</li>
);
})}
</ul>
</nav>
{/* Section Content */}
<div className="scrollbar-container flex-1 overflow-y-auto pr-4">
{currentSections.map((section) => (
<div
key={section.key}
className={cn(
activeSection === section.key ? "block" : "hidden",
)}
>
<ConfigSectionTemplate
sectionKey={section.key}
level="global"
onSave={handleSave}
showTitle={true}
/>
</div>
))}
</div>
</div>
</Tabs>
</div>
);
}