manage persistent group streaming settings

This commit is contained in:
Josh Hawkins 2024-11-13 07:40:18 -06:00
parent 685ef20fd5
commit a6cf755529
5 changed files with 92 additions and 14 deletions

View File

@ -1,7 +1,8 @@
import { import {
AllGroupsStreamingSettings,
CameraGroupConfig, CameraGroupConfig,
FrigateConfig, FrigateConfig,
GroupStreamingSettingsType, GroupStreamingSettings,
} from "@/types/frigateConfig"; } from "@/types/frigateConfig";
import { isDesktop, isMobile } from "react-device-detect"; import { isDesktop, isMobile } from "react-device-detect";
import useSWR from "swr"; import useSWR from "swr";
@ -75,8 +76,14 @@ import { CameraStreamingDialog } from "../settings/CameraStreamingDialog";
type CameraGroupSelectorProps = { type CameraGroupSelectorProps = {
className?: string; className?: string;
setAllGroupsStreamingSettings: React.Dispatch<
React.SetStateAction<AllGroupsStreamingSettings>
>;
}; };
export function CameraGroupSelector({ className }: CameraGroupSelectorProps) { export function CameraGroupSelector({
className,
setAllGroupsStreamingSettings,
}: CameraGroupSelectorProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
// tooltip // tooltip
@ -130,6 +137,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
activeGroup={group} activeGroup={group}
setGroup={setGroup} setGroup={setGroup}
deleteGroup={deleteGroup} deleteGroup={deleteGroup}
setAllGroupsStreamingSettings={setAllGroupsStreamingSettings}
/> />
<Scroller className={`${isMobile ? "whitespace-nowrap" : ""}`}> <Scroller className={`${isMobile ? "whitespace-nowrap" : ""}`}>
<div <div
@ -219,6 +227,9 @@ type NewGroupDialogProps = {
activeGroup?: string; activeGroup?: string;
setGroup: (value: string | undefined, replace?: boolean | undefined) => void; setGroup: (value: string | undefined, replace?: boolean | undefined) => void;
deleteGroup: () => void; deleteGroup: () => void;
setAllGroupsStreamingSettings: React.Dispatch<
React.SetStateAction<AllGroupsStreamingSettings>
>;
}; };
function NewGroupDialog({ function NewGroupDialog({
open, open,
@ -227,6 +238,7 @@ function NewGroupDialog({
activeGroup, activeGroup,
setGroup, setGroup,
deleteGroup, deleteGroup,
setAllGroupsStreamingSettings,
}: NewGroupDialogProps) { }: NewGroupDialogProps) {
const { mutate: updateConfig } = useSWR<FrigateConfig>("config"); const { mutate: updateConfig } = useSWR<FrigateConfig>("config");
@ -409,6 +421,7 @@ function NewGroupDialog({
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
onSave={onSave} onSave={onSave}
onCancel={onCancel} onCancel={onCancel}
setAllGroupsStreamingSettings={setAllGroupsStreamingSettings}
/> />
</> </>
)} )}
@ -423,12 +436,16 @@ type EditGroupDialogProps = {
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
currentGroups: [string, CameraGroupConfig][]; currentGroups: [string, CameraGroupConfig][];
activeGroup?: string; activeGroup?: string;
setAllGroupsStreamingSettings: React.Dispatch<
React.SetStateAction<AllGroupsStreamingSettings>
>;
}; };
export function EditGroupDialog({ export function EditGroupDialog({
open, open,
setOpen, setOpen,
currentGroups, currentGroups,
activeGroup, activeGroup,
setAllGroupsStreamingSettings,
}: EditGroupDialogProps) { }: EditGroupDialogProps) {
const Overlay = isDesktop ? Dialog : MobilePage; const Overlay = isDesktop ? Dialog : MobilePage;
const Content = isDesktop ? DialogContent : MobilePageContent; const Content = isDesktop ? DialogContent : MobilePageContent;
@ -480,6 +497,7 @@ export function EditGroupDialog({
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
onSave={() => setOpen(false)} onSave={() => setOpen(false)}
onCancel={() => setOpen(false)} onCancel={() => setOpen(false)}
setAllGroupsStreamingSettings={setAllGroupsStreamingSettings}
/> />
</div> </div>
</Content> </Content>
@ -600,6 +618,9 @@ type CameraGroupEditProps = {
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>; setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
onSave?: () => void; onSave?: () => void;
onCancel?: () => void; onCancel?: () => void;
setAllGroupsStreamingSettings: React.Dispatch<
React.SetStateAction<AllGroupsStreamingSettings>
>;
}; };
export function CameraGroupEdit({ export function CameraGroupEdit({
@ -609,17 +630,16 @@ export function CameraGroupEdit({
setIsLoading, setIsLoading,
onSave, onSave,
onCancel, onCancel,
setAllGroupsStreamingSettings,
}: CameraGroupEditProps) { }: CameraGroupEditProps) {
const { data: config, mutate: updateConfig } = const { data: config, mutate: updateConfig } =
useSWR<FrigateConfig>("config"); useSWR<FrigateConfig>("config");
const [groupStreamingSettings, setGroupStreamingSettings] = const [groupStreamingSettings, setGroupStreamingSettings] =
useState<GroupStreamingSettingsType>({}); useState<GroupStreamingSettings>({});
const [persistedGroupStreamingSettings, setPersistedGroupStreamingSettings] = const [persistedGroupStreamingSettings, setPersistedGroupStreamingSettings] =
usePersistence<{ [groupName: string]: GroupStreamingSettingsType }>( usePersistence<AllGroupsStreamingSettings>("streaming-settings");
"streaming-settings",
);
const birdseyeConfig = useMemo(() => config?.birdseye, [config]); const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
@ -671,9 +691,7 @@ export function CameraGroupEdit({
setIsLoading(true); setIsLoading(true);
// update streaming settings // update streaming settings
const updatedSettings: { const updatedSettings: AllGroupsStreamingSettings = {
[groupName: string]: GroupStreamingSettingsType;
} = {
...Object.fromEntries( ...Object.fromEntries(
Object.entries(persistedGroupStreamingSettings || {}).filter( Object.entries(persistedGroupStreamingSettings || {}).filter(
([key]) => key !== editingGroup?.[0], ([key]) => key !== editingGroup?.[0],
@ -715,6 +733,7 @@ export function CameraGroupEdit({
onSave(); onSave();
} }
await setPersistedGroupStreamingSettings(updatedSettings); await setPersistedGroupStreamingSettings(updatedSettings);
setAllGroupsStreamingSettings(updatedSettings);
} else { } else {
toast.error(`Failed to save config changes: ${res.statusText}`, { toast.error(`Failed to save config changes: ${res.statusText}`, {
position: "top-center", position: "top-center",
@ -740,6 +759,7 @@ export function CameraGroupEdit({
groupStreamingSettings, groupStreamingSettings,
setPersistedGroupStreamingSettings, setPersistedGroupStreamingSettings,
persistedGroupStreamingSettings, persistedGroupStreamingSettings,
setAllGroupsStreamingSettings,
], ],
); );

View File

@ -6,7 +6,9 @@ import GeneralSettings from "../menu/GeneralSettings";
import AccountSettings from "../menu/AccountSettings"; import AccountSettings from "../menu/AccountSettings";
import useNavigation from "@/hooks/use-navigation"; import useNavigation from "@/hooks/use-navigation";
import { baseUrl } from "@/api/baseUrl"; import { baseUrl } from "@/api/baseUrl";
import { useMemo } from "react"; import { useEffect, useMemo, useState } from "react";
import { usePersistence } from "@/hooks/use-persistence";
import { AllGroupsStreamingSettings } from "@/types/frigateConfig";
function Sidebar() { function Sidebar() {
const basePath = useMemo(() => new URL(baseUrl).pathname, []); const basePath = useMemo(() => new URL(baseUrl).pathname, []);
@ -16,6 +18,18 @@ function Sidebar() {
const navbarLinks = useNavigation(); const navbarLinks = useNavigation();
const [, setAllGroupsStreamingSettings] =
useState<AllGroupsStreamingSettings>({});
const [persistedStreamingSettings, _, isStreamingSettingsLoaded] =
usePersistence<AllGroupsStreamingSettings>("streaming-settings");
useEffect(() => {
if (isStreamingSettingsLoaded) {
setAllGroupsStreamingSettings(persistedStreamingSettings ?? {});
}
}, [isStreamingSettingsLoaded, persistedStreamingSettings]);
return ( return (
<aside className="scrollbar-container scrollbar-hidden absolute inset-y-0 left-0 z-10 flex w-[52px] flex-col justify-between overflow-y-auto border-r border-secondary-highlight bg-background_alt py-4"> <aside className="scrollbar-container scrollbar-hidden absolute inset-y-0 left-0 z-10 flex w-[52px] flex-col justify-between overflow-y-auto border-r border-secondary-highlight bg-background_alt py-4">
<span tabIndex={0} className="sr-only" /> <span tabIndex={0} className="sr-only" />
@ -34,7 +48,12 @@ function Sidebar() {
item={item} item={item}
Icon={item.icon} Icon={item.icon}
/> />
{showCameraGroups && <CameraGroupSelector className="mb-4" />} {showCameraGroups && (
<CameraGroupSelector
setAllGroupsStreamingSettings={setAllGroupsStreamingSettings}
className="mb-4"
/>
)}
</div> </div>
); );
})} })}

View File

@ -235,10 +235,14 @@ export type CameraStreamingSettings = {
compatibilityMode: boolean; compatibilityMode: boolean;
}; };
export type GroupStreamingSettingsType = { export type GroupStreamingSettings = {
[cameraName: string]: CameraStreamingSettings; [cameraName: string]: CameraStreamingSettings;
}; };
export type AllGroupsStreamingSettings = {
[groupName: string]: GroupStreamingSettings;
};
export interface FrigateConfig { export interface FrigateConfig {
audio: { audio: {
enabled: boolean; enabled: boolean;

View File

@ -1,5 +1,6 @@
import { usePersistence } from "@/hooks/use-persistence"; import { usePersistence } from "@/hooks/use-persistence";
import { import {
AllGroupsStreamingSettings,
BirdseyeConfig, BirdseyeConfig,
CameraConfig, CameraConfig,
FrigateConfig, FrigateConfig,
@ -56,6 +57,9 @@ type DraggableGridLayoutProps = {
setIsEditMode: React.Dispatch<React.SetStateAction<boolean>>; setIsEditMode: React.Dispatch<React.SetStateAction<boolean>>;
fullscreen: boolean; fullscreen: boolean;
toggleFullscreen: () => void; toggleFullscreen: () => void;
setAllGroupsStreamingSettings: React.Dispatch<
React.SetStateAction<AllGroupsStreamingSettings>
>;
}; };
export default function DraggableGridLayout({ export default function DraggableGridLayout({
cameras, cameras,
@ -70,6 +74,7 @@ export default function DraggableGridLayout({
setIsEditMode, setIsEditMode,
fullscreen, fullscreen,
toggleFullscreen, toggleFullscreen,
setAllGroupsStreamingSettings,
}: DraggableGridLayoutProps) { }: DraggableGridLayoutProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const birdseyeConfig = useMemo(() => config?.birdseye, [config]); const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
@ -372,6 +377,7 @@ export default function DraggableGridLayout({
setOpen={setEditGroup} setOpen={setEditGroup}
currentGroups={groups} currentGroups={groups}
activeGroup={group} activeGroup={group}
setAllGroupsStreamingSettings={setAllGroupsStreamingSettings}
/> />
<ResponsiveGridLayout <ResponsiveGridLayout
className="grid-layout" className="grid-layout"

View File

@ -14,7 +14,11 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { usePersistence } from "@/hooks/use-persistence"; import { usePersistence } from "@/hooks/use-persistence";
import { CameraConfig, FrigateConfig } from "@/types/frigateConfig"; import {
AllGroupsStreamingSettings,
CameraConfig,
FrigateConfig,
} from "@/types/frigateConfig";
import { ReviewSegment } from "@/types/review"; import { ReviewSegment } from "@/types/review";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { import {
@ -187,6 +191,28 @@ export default function LiveDashboardView({
const { preferredLiveModes, setPreferredLiveModes, resetPreferredLiveMode } = const { preferredLiveModes, setPreferredLiveModes, resetPreferredLiveMode } =
useCameraLiveMode(cameras, windowVisible); useCameraLiveMode(cameras, windowVisible);
const [allGroupsStreamingSettings, setAllGroupsStreamingSettings] =
useState<AllGroupsStreamingSettings>({});
const [persistedStreamingSettings, _, isStreamingSettingsLoaded] =
usePersistence<AllGroupsStreamingSettings>("streaming-settings");
const currentGroupStreamingSettings = useMemo(() => {
if (cameraGroup && cameraGroup != "default" && allGroupsStreamingSettings) {
return allGroupsStreamingSettings[cameraGroup];
}
}, [allGroupsStreamingSettings, cameraGroup]);
useEffect(() => {
if (isStreamingSettingsLoaded) {
setAllGroupsStreamingSettings(persistedStreamingSettings ?? {});
}
}, [isStreamingSettingsLoaded, persistedStreamingSettings]);
useEffect(() => {
// console.log("group settings", cameraGroup, currentGroupStreamingSettings);
}, [currentGroupStreamingSettings, cameraGroup]);
const cameraRef = useCallback( const cameraRef = useCallback(
(node: HTMLElement | null) => { (node: HTMLElement | null) => {
if (!visibleCameraObserver.current) { if (!visibleCameraObserver.current) {
@ -230,7 +256,9 @@ export default function LiveDashboardView({
<div className="relative flex h-11 items-center justify-between"> <div className="relative flex h-11 items-center justify-between">
<Logo className="absolute inset-x-1/2 h-8 -translate-x-1/2" /> <Logo className="absolute inset-x-1/2 h-8 -translate-x-1/2" />
<div className="max-w-[45%]"> <div className="max-w-[45%]">
<CameraGroupSelector /> <CameraGroupSelector
setAllGroupsStreamingSettings={setAllGroupsStreamingSettings}
/>
</div> </div>
{(!cameraGroup || cameraGroup == "default" || isMobileOnly) && ( {(!cameraGroup || cameraGroup == "default" || isMobileOnly) && (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@ -408,6 +436,7 @@ export default function LiveDashboardView({
setIsEditMode={setIsEditMode} setIsEditMode={setIsEditMode}
fullscreen={fullscreen} fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen} toggleFullscreen={toggleFullscreen}
setAllGroupsStreamingSettings={setAllGroupsStreamingSettings}
/> />
)} )}
</div> </div>