mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-05 10:45:21 +03:00
toggle viewing of advanced options
- fix AppBar render order, its tooltips are visible now
This commit is contained in:
parent
9e531b0b5b
commit
160e9ba336
@ -85,6 +85,10 @@ class UIConfig(FrigateBaseModel):
|
|||||||
strftime_fmt: Optional[str] = Field(
|
strftime_fmt: Optional[str] = Field(
|
||||||
default=None, title="Override date and time format using strftime syntax."
|
default=None, title="Override date and time format using strftime syntax."
|
||||||
)
|
)
|
||||||
|
show_advanced_options: bool = Field(
|
||||||
|
default=True,
|
||||||
|
title="Default setting to show Advanced Options, such as Config, Camera Rec/Motion/Snap buttons, go2rtc dashboard and more.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StatsConfig(FrigateBaseModel):
|
class StatsConfig(FrigateBaseModel):
|
||||||
|
|||||||
@ -5,9 +5,10 @@ import Menu, { MenuItem, MenuSeparator } from './components/Menu';
|
|||||||
import AutoAwesomeIcon from './icons/AutoAwesome';
|
import AutoAwesomeIcon from './icons/AutoAwesome';
|
||||||
import LightModeIcon from './icons/LightMode';
|
import LightModeIcon from './icons/LightMode';
|
||||||
import DarkModeIcon from './icons/DarkMode';
|
import DarkModeIcon from './icons/DarkMode';
|
||||||
|
import SettingsIcon from './icons/Settings';
|
||||||
import FrigateRestartIcon from './icons/FrigateRestart';
|
import FrigateRestartIcon from './icons/FrigateRestart';
|
||||||
import Prompt from './components/Prompt';
|
import Prompt from './components/Prompt';
|
||||||
import { useDarkMode } from './context';
|
import { useDarkMode, useAdvOptions } from './context';
|
||||||
import { useCallback, useRef, useState } from 'preact/hooks';
|
import { useCallback, useRef, useState } from 'preact/hooks';
|
||||||
import { useRestart } from './api/ws';
|
import { useRestart } from './api/ws';
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ export default function AppBar() {
|
|||||||
const [showMoreMenu, setShowMoreMenu] = useState(false);
|
const [showMoreMenu, setShowMoreMenu] = useState(false);
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const [showDialogWait, setShowDialogWait] = useState(false);
|
const [showDialogWait, setShowDialogWait] = useState(false);
|
||||||
|
const { showAdvOptions, setShowAdvOptions } = useAdvOptions();
|
||||||
const { setDarkMode } = useDarkMode();
|
const { setDarkMode } = useDarkMode();
|
||||||
const { send: sendRestart } = useRestart();
|
const { send: sendRestart } = useRestart();
|
||||||
|
|
||||||
@ -26,6 +28,11 @@ export default function AppBar() {
|
|||||||
[setDarkMode, setShowMoreMenu]
|
[setDarkMode, setShowMoreMenu]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleToggleAdvOptions = useCallback(() => {
|
||||||
|
setShowAdvOptions(showAdvOptions === 1 ? 0 : 1);
|
||||||
|
setShowMoreMenu(false);
|
||||||
|
},[showAdvOptions, setShowAdvOptions, setShowMoreMenu]);
|
||||||
|
|
||||||
const moreRef = useRef(null);
|
const moreRef = useRef(null);
|
||||||
|
|
||||||
const handleShowMenu = useCallback(() => {
|
const handleShowMenu = useCallback(() => {
|
||||||
@ -61,6 +68,8 @@ export default function AppBar() {
|
|||||||
<MenuItem icon={LightModeIcon} label="Light" value="light" onSelect={handleSelectDarkMode} />
|
<MenuItem icon={LightModeIcon} label="Light" value="light" onSelect={handleSelectDarkMode} />
|
||||||
<MenuItem icon={DarkModeIcon} label="Dark" value="dark" onSelect={handleSelectDarkMode} />
|
<MenuItem icon={DarkModeIcon} label="Dark" value="dark" onSelect={handleSelectDarkMode} />
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
|
<MenuItem icon={SettingsIcon} label={showAdvOptions ? 'Hide Adv. Options' : 'Show Adv. Options'} onSelect={handleToggleAdvOptions} />
|
||||||
|
<MenuSeparator />
|
||||||
<MenuItem icon={FrigateRestartIcon} label="Restart Frigate" onSelect={handleRestart} />
|
<MenuItem icon={FrigateRestartIcon} label="Restart Frigate" onSelect={handleRestart} />
|
||||||
</Menu>
|
</Menu>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@ -4,11 +4,13 @@ import { Match } from 'preact-router/match';
|
|||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
import { ENV } from './env';
|
import { ENV } from './env';
|
||||||
import { useMemo } from 'preact/hooks'
|
import { useMemo } from 'preact/hooks'
|
||||||
|
import { useAdvOptions } from './context';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import NavigationDrawer, { Destination, Separator } from './components/NavigationDrawer';
|
import NavigationDrawer, { Destination, Separator } from './components/NavigationDrawer';
|
||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
const { data: config } = useSWR('config');
|
const { data: config } = useSWR('config');
|
||||||
|
const { showAdvOptions } = useAdvOptions();
|
||||||
|
|
||||||
const sortedCameras = useMemo(() => {
|
const sortedCameras = useMemo(() => {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@ -48,7 +50,7 @@ export default function Sidebar() {
|
|||||||
<Separator />
|
<Separator />
|
||||||
<Destination href="/storage" text="Storage" />
|
<Destination href="/storage" text="Storage" />
|
||||||
<Destination href="/system" text="System" />
|
<Destination href="/system" text="System" />
|
||||||
<Destination href="/config" text="Config" />
|
{ showAdvOptions ? <Destination href="/config" text="Config" /> : null}
|
||||||
<Destination href="/logs" text="Logs" />
|
<Destination href="/logs" text="Logs" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="flex flex-grow" />
|
<div className="flex flex-grow" />
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import AppBar from './AppBar';
|
|||||||
import Cameras from './routes/Cameras';
|
import Cameras from './routes/Cameras';
|
||||||
import { Router } from 'preact-router';
|
import { Router } from 'preact-router';
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
import { DarkModeProvider, DrawerProvider } from './context';
|
import { DarkModeProvider, DrawerProvider, AdvOptionsProvider } from './context';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@ -16,8 +16,8 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<DarkModeProvider>
|
<DarkModeProvider>
|
||||||
<DrawerProvider>
|
<DrawerProvider>
|
||||||
|
<AdvOptionsProvider>
|
||||||
<div data-testid="app" className="w-full">
|
<div data-testid="app" className="w-full">
|
||||||
<AppBar />
|
|
||||||
{!config ? (
|
{!config ? (
|
||||||
<div className="flex flex-grow-1 min-h-screen justify-center items-center">
|
<div className="flex flex-grow-1 min-h-screen justify-center items-center">
|
||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
@ -25,6 +25,7 @@ export default function App() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex flex-row min-h-screen w-full bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
|
<div className="flex flex-row min-h-screen w-full bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
<AppBar />
|
||||||
<div className="w-full flex-auto mt-16 min-w-0">
|
<div className="w-full flex-auto mt-16 min-w-0">
|
||||||
<Router>
|
<Router>
|
||||||
<AsyncRoute path="/cameras/:camera/editor" getComponent={Routes.getCameraMap} />
|
<AsyncRoute path="/cameras/:camera/editor" getComponent={Routes.getCameraMap} />
|
||||||
@ -47,6 +48,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</AdvOptionsProvider>
|
||||||
</DrawerProvider>
|
</DrawerProvider>
|
||||||
</DarkModeProvider>
|
</DarkModeProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { h } from 'preact';
|
|||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import MenuIcon from '../icons/Menu';
|
import MenuIcon from '../icons/Menu';
|
||||||
import MoreIcon from '../icons/More';
|
import MoreIcon from '../icons/More';
|
||||||
import { useDrawer } from '../context';
|
import { About } from '../icons/About';
|
||||||
|
import { useDrawer, useAdvOptions } from '../context';
|
||||||
import { useLayoutEffect, useCallback, useState } from 'preact/hooks';
|
import { useLayoutEffect, useCallback, useState } from 'preact/hooks';
|
||||||
|
|
||||||
// We would typically preserve these in component state
|
// We would typically preserve these in component state
|
||||||
@ -13,6 +14,7 @@ export default function AppBar({ title: Title, overflowRef, onOverflowClick }) {
|
|||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
const [atZero, setAtZero] = useState(window.scrollY === 0);
|
const [atZero, setAtZero] = useState(window.scrollY === 0);
|
||||||
const { setShowDrawer } = useDrawer();
|
const { setShowDrawer } = useDrawer();
|
||||||
|
const { showAdvOptions } = useAdvOptions();
|
||||||
|
|
||||||
const scrollListener = useCallback(() => {
|
const scrollListener = useCallback(() => {
|
||||||
const scrollY = window.scrollY;
|
const scrollY = window.scrollY;
|
||||||
@ -38,7 +40,7 @@ export default function AppBar({ title: Title, overflowRef, onOverflowClick }) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="appbar"
|
id="appbar"
|
||||||
className={`w-full border-b border-gray-200 dark:border-gray-700 flex items-center align-middle p-2 fixed left-0 right-0 z-10 bg-white dark:bg-gray-900 transform transition-all duration-200 ${
|
className={`w-full border-b border-gray-200 dark:border-gray-700 flex items-center align-middle p-2 fixed left-0 right-0 bg-white dark:bg-gray-900 transform transition-all duration-200 ${
|
||||||
!show ? '-translate-y-full' : 'translate-y-0'
|
!show ? '-translate-y-full' : 'translate-y-0'
|
||||||
} ${!atZero ? 'shadow-sm' : ''}`}
|
} ${!atZero ? 'shadow-sm' : ''}`}
|
||||||
data-testid="appbar"
|
data-testid="appbar"
|
||||||
@ -50,6 +52,19 @@ export default function AppBar({ title: Title, overflowRef, onOverflowClick }) {
|
|||||||
</div>
|
</div>
|
||||||
<Title />
|
<Title />
|
||||||
<div className="flex-grow-1 flex justify-end w-full">
|
<div className="flex-grow-1 flex justify-end w-full">
|
||||||
|
{ !showAdvOptions ?
|
||||||
|
<Button
|
||||||
|
className="rounded-full"
|
||||||
|
type="text"
|
||||||
|
color="gray"
|
||||||
|
aria-label="Advanced Options are hidden via the nearby menu."
|
||||||
|
>
|
||||||
|
<About className="w-6" />
|
||||||
|
</Button>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="flex-grow-1 flex">
|
||||||
{overflowRef && onOverflowClick ? (
|
{overflowRef && onOverflowClick ? (
|
||||||
<div className="w-auto" ref={overflowRef}>
|
<div className="w-auto" ref={overflowRef}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -1,6 +1,29 @@
|
|||||||
import { h, createContext } from 'preact';
|
import { h, createContext } from 'preact';
|
||||||
import { get as getData, set as setData } from 'idb-keyval';
|
import { get as getData, set as setData } from 'idb-keyval';
|
||||||
import { useCallback, useContext, useEffect, useLayoutEffect, useState } from 'preact/hooks';
|
import { useCallback, useContext, useEffect, useLayoutEffect, useState } from 'preact/hooks';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
const AdvOptions = createContext(null);
|
||||||
|
|
||||||
|
export function AdvOptionsProvider({ children }) {
|
||||||
|
const [showAdvOptions, setShowAdvOptions] = usePersistence('show-advanced-options', null);
|
||||||
|
const { data: config } = useSWR('config');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
const configValue = config.ui.show_advanced_options == true? 1 : 0; //fixes a load error
|
||||||
|
setShowAdvOptions(showAdvOptions || configValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
}, [setShowAdvOptions, config]);
|
||||||
|
|
||||||
|
return <AdvOptions.Provider value={{ showAdvOptions, setShowAdvOptions }}>{children}</AdvOptions.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAdvOptions() {
|
||||||
|
return useContext(AdvOptions);
|
||||||
|
}
|
||||||
|
|
||||||
const DarkMode = createContext(null);
|
const DarkMode = createContext(null);
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import Link from '../components/Link';
|
|||||||
import SettingsIcon from '../icons/Settings';
|
import SettingsIcon from '../icons/Settings';
|
||||||
import Switch from '../components/Switch';
|
import Switch from '../components/Switch';
|
||||||
import ButtonsTabbed from '../components/ButtonsTabbed';
|
import ButtonsTabbed from '../components/ButtonsTabbed';
|
||||||
import { usePersistence } from '../context';
|
import { usePersistence, useAdvOptions } from '../context';
|
||||||
import { useCallback, useMemo, useState } from 'preact/hooks';
|
import { useCallback, useMemo, useState } from 'preact/hooks';
|
||||||
import { useApiHost } from '../api';
|
import { useApiHost } from '../api';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
@ -25,6 +25,7 @@ export default function Camera({ camera }) {
|
|||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const [viewMode, setViewMode] = useState('live');
|
const [viewMode, setViewMode] = useState('live');
|
||||||
|
const { showAdvOptions } = useAdvOptions();
|
||||||
|
|
||||||
const cameraConfig = config?.cameras[camera];
|
const cameraConfig = config?.cameras[camera];
|
||||||
const restreamEnabled =
|
const restreamEnabled =
|
||||||
@ -108,7 +109,7 @@ export default function Camera({ camera }) {
|
|||||||
label="Regions"
|
label="Regions"
|
||||||
labelPosition="after"
|
labelPosition="after"
|
||||||
/>
|
/>
|
||||||
<Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link>
|
{showAdvOptions ? <Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link> : null}
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import MotionIcon from '../icons/Motion';
|
|||||||
import SnapshotIcon from '../icons/Snapshot';
|
import SnapshotIcon from '../icons/Snapshot';
|
||||||
import { useDetectState, useRecordingsState, useSnapshotsState } from '../api/ws';
|
import { useDetectState, useRecordingsState, useSnapshotsState } from '../api/ws';
|
||||||
import { useMemo } from 'preact/hooks';
|
import { useMemo } from 'preact/hooks';
|
||||||
|
import { useAdvOptions } from '../context';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
export default function Cameras() {
|
export default function Cameras() {
|
||||||
@ -86,7 +87,9 @@ function Camera({ name, config }) {
|
|||||||
[config, detectValue, sendDetect, recordValue, sendRecordings, snapshotValue, sendSnapshots]
|
[config, detectValue, sendDetect, recordValue, sendRecordings, snapshotValue, sendSnapshots]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { showAdvOptions } = useAdvOptions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card buttons={buttons} href={href} header={cleanName} icons={icons} media={<CameraImage camera={name} stretch />} />
|
<Card buttons={buttons} href={href} header={cleanName} icons={showAdvOptions ? icons : []} media={<CameraImage camera={name} stretch />} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { formatUnixTimestampToDateTime, getDurationFromTimestamps } from '../uti
|
|||||||
import TimeAgo from '../components/TimeAgo';
|
import TimeAgo from '../components/TimeAgo';
|
||||||
import Timepicker from '../components/TimePicker';
|
import Timepicker from '../components/TimePicker';
|
||||||
import TimelineSummary from '../components/TimelineSummary';
|
import TimelineSummary from '../components/TimelineSummary';
|
||||||
|
import { useAdvOptions } from '../context';
|
||||||
|
|
||||||
const API_LIMIT = 25;
|
const API_LIMIT = 25;
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ const monthsAgo = (num) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Events({ path, ...props }) {
|
export default function Events({ path, ...props }) {
|
||||||
|
const { showAdvOptions } = useAdvOptions();
|
||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
const [searchParams, setSearchParams] = useState({
|
const [searchParams, setSearchParams] = useState({
|
||||||
before: null,
|
before: null,
|
||||||
@ -662,8 +664,9 @@ export default function Events({ path, ...props }) {
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<Delete
|
<Delete
|
||||||
className="h-6 w-6 cursor-pointer"
|
className="h-6 w-6 cursor-pointer"
|
||||||
stroke="#f87171"
|
stroke={showAdvOptions ? "#f87171" : "lightgrey"}
|
||||||
onClick={(e) => onDelete(e, event.id, event.retain_indefinitely)}
|
onClick={showAdvOptions ? (e) => onDelete(e, event.id, event.retain_indefinitely) : null}
|
||||||
|
aria-label="Advanced Options are hidden via the nearby menu."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Download
|
<Download
|
||||||
|
|||||||
@ -12,12 +12,14 @@ import Dialog from '../components/Dialog';
|
|||||||
import TimeAgo from '../components/TimeAgo';
|
import TimeAgo from '../components/TimeAgo';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import { About } from '../icons/About';
|
import { About } from '../icons/About';
|
||||||
|
import { useAdvOptions } from '../context';
|
||||||
|
|
||||||
const emptyObject = Object.freeze({});
|
const emptyObject = Object.freeze({});
|
||||||
|
|
||||||
export default function System() {
|
export default function System() {
|
||||||
const [state, setState] = useState({ showFfprobe: false, ffprobe: '' });
|
const [state, setState] = useState({ showFfprobe: false, ffprobe: '' });
|
||||||
const { data: config } = useSWR('config');
|
const { data: config } = useSWR('config');
|
||||||
|
const { showAdvOptions } = useAdvOptions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
value: { payload: stats },
|
value: { payload: stats },
|
||||||
@ -98,6 +100,7 @@ export default function System() {
|
|||||||
{config && (
|
{config && (
|
||||||
<span class="p-1">
|
<span class="p-1">
|
||||||
go2rtc {go2rtc && `${go2rtc.version} `}
|
go2rtc {go2rtc && `${go2rtc.version} `}
|
||||||
|
{showAdvOptions ?
|
||||||
<Link
|
<Link
|
||||||
className="text-blue-500 hover:underline"
|
className="text-blue-500 hover:underline"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -106,6 +109,8 @@ export default function System() {
|
|||||||
>
|
>
|
||||||
dashboard
|
dashboard
|
||||||
</Link>
|
</Link>
|
||||||
|
: null
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user