diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index 5d8638745..23add70d6 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -431,4 +431,12 @@ cameras: quality: 70 # Optional: Restrict mqtt messages to objects that entered any of the listed zones (default: no required zones) required_zones: [] + + # Optional: Configuration for how camera is handled in the GUI. + ui: + # Optional: Adjust sort order of cameras in the GUI. Larger numbers come later (default: shown below) + # By default the cameras are sorted alphabetically. + order: 0 + # Optional: Whether or not to show the camera in the GUI (default: shown below) + show: True ``` diff --git a/frigate/config.py b/frigate/config.py index a81f3241a..1937a66f3 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -499,6 +499,11 @@ class CameraLiveConfig(FrigateBaseModel): quality: int = Field(default=8, ge=1, le=31, title="Live camera view quality") +class CameraUiConfig(FrigateBaseModel): + order: int = Field(default=0, title="Order of camera in GUI.") + show: bool = Field(default=True, title="Show this camera in Frigate GUI.") + + class CameraConfig(FrigateBaseModel): name: Optional[str] = Field(title="Camera name.", regex="^[a-zA-Z0-9_-]+$") ffmpeg: CameraFfmpegConfig = Field(title="FFmpeg configuration for the camera.") @@ -531,6 +536,9 @@ class CameraConfig(FrigateBaseModel): detect: DetectConfig = Field( default_factory=DetectConfig, title="Object detection configuration." ) + ui: CameraUiConfig = Field( + default_factory=CameraUiConfig, title="Camera UI Modifications." + ) timestamp_style: TimestampStyleConfig = Field( default_factory=TimestampStyleConfig, title="Timestamp style configuration." ) diff --git a/web/config/handlers.js b/web/config/handlers.js index e2e95d077..7c2fd883e 100644 --- a/web/config/handlers.js +++ b/web/config/handlers.js @@ -20,6 +20,7 @@ export const handlers = [ detect: { width: 1280, height: 720 }, snapshots: {}, live: { height: 720 }, + ui: { show: true, order: 0 }, }, side: { name: 'side', @@ -28,6 +29,7 @@ export const handlers = [ detect: { width: 1280, height: 720 }, snapshots: {}, live: { height: 720 }, + ui: { show: true, order: 1 }, }, }, }) diff --git a/web/src/Sidebar.jsx b/web/src/Sidebar.jsx index 6e80725cf..fa8c78c46 100644 --- a/web/src/Sidebar.jsx +++ b/web/src/Sidebar.jsx @@ -3,6 +3,7 @@ import LinkedLogo from './components/LinkedLogo'; import { Match } from 'preact-router/match'; import { memo } from 'preact/compat'; import { ENV } from './env'; +import { useMemo } from 'preact/hooks' import useSWR from 'swr'; import NavigationDrawer, { Destination, Separator } from './components/NavigationDrawer'; @@ -19,35 +20,14 @@ export default function Sidebar() { {({ matches }) => matches ? ( - - - {Object.entries(cameras).map(([camera]) => ( - - ))} - - + ) : null } {({ matches }) => matches ? ( - - - {Object.entries(cameras).map(([camera, conf]) => { - if (conf.record.enabled) { - return ( - - ); - } - return null; - })} - - + ) : null } @@ -68,10 +48,58 @@ export default function Sidebar() { ); } +function SortedCameras({ unsortedCameras }) { + + const sortedCameras = useMemo(() => + Object.entries(unsortedCameras) + .filter(([_, conf]) => conf.ui.show) + .sort(([_, aConf], [__, bConf]) => aConf.ui.order === bConf.ui.order ? 0 : (aConf.ui.order > bConf.ui.order ? 1 : -1)), + [unsortedCameras]); + + return ( + + + {sortedCameras.map(([camera]) => ( + + ))} + + + ); +} + +function SortedRecordingCameras({ unsortedCameras }) { + + const sortedCameras = useMemo(() => + Object.entries(unsortedCameras) + .filter(([_, conf]) => conf.ui.show) + .sort(([_, aConf], [__, bConf]) => aConf.ui.order === bConf.ui.order ? 0 : (aConf.ui.order > bConf.ui.order ? 1 : -1)), + [unsortedCameras]); + + return ( + + + {sortedCameras.map(([camera, conf]) => { + if (conf.record.enabled) { + return ( + + ); + } + return null; + })} + + + ); +} + const Header = memo(() => { return (
); -}); +}); \ No newline at end of file diff --git a/web/src/routes/Cameras.jsx b/web/src/routes/Cameras.jsx index efe2e9b7c..1933874d0 100644 --- a/web/src/routes/Cameras.jsx +++ b/web/src/routes/Cameras.jsx @@ -1,4 +1,4 @@ -import { h } from 'preact'; +import { h, Fragment } from 'preact'; import ActivityIndicator from '../components/ActivityIndicator'; import Card from '../components/Card'; import CameraImage from '../components/CameraImage'; @@ -16,10 +16,25 @@ export default function Cameras() { ) : (
- {Object.entries(config.cameras).map(([camera, conf]) => ( + +
+ ); +} + +function SortedCameras({ unsortedCameras }) { + + const sortedCameras = useMemo(() => + Object.entries(unsortedCameras) + .filter(([_, conf]) => conf.ui.show) + .sort(([_, aConf], [__, bConf]) => aConf.ui.order === bConf.ui.order ? 0 : (aConf.ui.order > bConf.ui.order ? 1 : -1)), + [unsortedCameras]); + + return ( + + {sortedCameras.map(([camera, conf]) => ( ))} - + ); } @@ -67,4 +82,4 @@ function Camera({ name }) { return ( } /> ); -} +} \ No newline at end of file