mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2024-12-25 22:55:55 -05:00
Add store in url for layout and all cameras
Signed-off-by: Damian Krysta <damian@krysta.dev>
This commit is contained in:
parent
552f6bf19d
commit
56918bf5c2
@ -11,7 +11,7 @@ import { useSnackbars } from "./snackbars";
|
|||||||
import { Camera, Session } from "./types";
|
import { Camera, Session } from "./types";
|
||||||
import ListActivity from "./List";
|
import ListActivity from "./List";
|
||||||
import AppBar from "@mui/material/AppBar";
|
import AppBar from "@mui/material/AppBar";
|
||||||
import {BrowserRouter, Routes, Route, Link} from "react-router-dom";
|
import {Routes, Route, Link, useSearchParams, useResolvedPath, useMatch} from "react-router-dom";
|
||||||
import LiveActivity, { MultiviewChooser } from "./Live";
|
import LiveActivity, { MultiviewChooser } from "./Live";
|
||||||
import Drawer from "@mui/material/Drawer";
|
import Drawer from "@mui/material/Drawer";
|
||||||
import List from "@mui/material/List";
|
import List from "@mui/material/List";
|
||||||
@ -34,12 +34,16 @@ type Activity = "list" | "live";
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [showMenu, toggleShowMenu] = useReducer((m: boolean) => !m, false);
|
const [showMenu, toggleShowMenu] = useReducer((m: boolean) => !m, false);
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const [showListSelectors, toggleShowListSelectors] = useReducer(
|
const [showListSelectors, toggleShowListSelectors] = useReducer(
|
||||||
(m: boolean) => !m,
|
(m: boolean) => !m,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const [activity, setActivity] = useState<Activity>("list");
|
let resolved = useResolvedPath('live');
|
||||||
const [multiviewLayoutIndex, setMultiviewLayoutIndex] = useState(0);
|
let match = useMatch({ path: resolved.pathname, end: true });
|
||||||
|
const [activity, setActivity] = useState<Activity>(match ? "live" : "list");
|
||||||
|
const [multiviewLayoutIndex, setMultiviewLayoutIndex] = useState(Number.parseInt(searchParams.get('layout') || '0', 10));
|
||||||
const [session, setSession] = useState<Session | null>(null);
|
const [session, setSession] = useState<Session | null>(null);
|
||||||
const [cameras, setCameras] = useState<Camera[] | null>(null);
|
const [cameras, setCameras] = useState<Camera[] | null>(null);
|
||||||
const [timeZoneName, setTimeZoneName] = useState<string | null>(null);
|
const [timeZoneName, setTimeZoneName] = useState<string | null>(null);
|
||||||
@ -145,14 +149,17 @@ function App() {
|
|||||||
activityMenu = (
|
activityMenu = (
|
||||||
<MultiviewChooser
|
<MultiviewChooser
|
||||||
layoutIndex={multiviewLayoutIndex}
|
layoutIndex={multiviewLayoutIndex}
|
||||||
onChoice={setMultiviewLayoutIndex}
|
onChoice={(value) => {
|
||||||
|
setMultiviewLayoutIndex(value)
|
||||||
|
setSearchParams({layout: value.toString()}, )
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<>
|
||||||
<AppBar position="static">
|
<AppBar position="static">
|
||||||
<MoonfireMenu
|
<MoonfireMenu
|
||||||
loginState={loginState}
|
loginState={loginState}
|
||||||
@ -174,18 +181,17 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<List>
|
<List>
|
||||||
<ListItem button key="list" onClick={() => clickActivity("list")}>
|
<ListItem button key="list" onClick={() => clickActivity("list")} component={Link} to="/">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<ListIcon />
|
<ListIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<Link to="/"><ListItemText primary="List view" /></Link>
|
<ListItemText primary="List view" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem button key="live" onClick={() => clickActivity("live")}>
|
<ListItem button key="live" onClick={() => clickActivity("live")} component={Link} to="/live">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Videocam />
|
<Videocam />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Live view (experimental)" />
|
||||||
<Link to="/live"><ListItemText primary="Live view (experimental)" /></Link>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
@ -211,10 +217,10 @@ function App() {
|
|||||||
</p>
|
</p>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
<Routes >
|
<Routes>
|
||||||
{fetchedCameras(cameras)}
|
{fetchedCameras(cameras)}
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import React, { useReducer } from "react";
|
import React, {useReducer} from "react";
|
||||||
import { Camera } from "../types";
|
import { Camera } from "../types";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { Theme } from "@mui/material/styles";
|
import { Theme } from "@mui/material/styles";
|
||||||
@ -91,13 +91,13 @@ export const MultiviewChooser = (props: MultiviewChooserProps) => {
|
|||||||
<Select
|
<Select
|
||||||
id="layout"
|
id="layout"
|
||||||
value={props.layoutIndex}
|
value={props.layoutIndex}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
props.onChoice(
|
props.onChoice(
|
||||||
typeof e.target.value === "string"
|
typeof e.target.value === "string"
|
||||||
? parseInt(e.target.value)
|
? parseInt(e.target.value)
|
||||||
: e.target.value
|
: e.target.value
|
||||||
)
|
)
|
||||||
}
|
}}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={{
|
||||||
// Hacky attempt to style for the app menu.
|
// Hacky attempt to style for the app menu.
|
||||||
@ -130,6 +130,7 @@ interface SelectOp {
|
|||||||
cameraIndex: number | null;
|
cameraIndex: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function selectedReducer(old: SelectedCameras, op: SelectOp): SelectedCameras {
|
function selectedReducer(old: SelectedCameras, op: SelectOp): SelectedCameras {
|
||||||
let selected = [...old]; // shallow clone.
|
let selected = [...old]; // shallow clone.
|
||||||
if (op.cameraIndex !== null) {
|
if (op.cameraIndex !== null) {
|
||||||
@ -151,15 +152,17 @@ function selectedReducer(old: SelectedCameras, op: SelectOp): SelectedCameras {
|
|||||||
* as possible.
|
* as possible.
|
||||||
*/
|
*/
|
||||||
const Multiview = (props: MultiviewProps) => {
|
const Multiview = (props: MultiviewProps) => {
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const [selected, updateSelected] = useReducer(
|
const [selected, updateSelected] = useReducer(
|
||||||
selectedReducer,
|
selectedReducer,
|
||||||
Array(MAX_CAMERAS).fill(null)
|
searchParams.has('cams') ? JSON.parse(searchParams.get('cams') || "") : Array(MAX_CAMERAS).fill(null)
|
||||||
);
|
);
|
||||||
const outerRef = React.useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
|
const outerRef = React.useRef<HTMLDivElement>(null);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const layout = LAYOUTS[props.layoutIndex];
|
const layout = LAYOUTS[props.layoutIndex];
|
||||||
|
|
||||||
const monoviews = selected.slice(0, layout.cameras).map((e, i) => {
|
const monoviews = selected.slice(0, layout.cameras).map((e, i) => {
|
||||||
// When a camera is selected, use the camera's index as the key.
|
// When a camera is selected, use the camera's index as the key.
|
||||||
// This allows swapping cameras' positions without tearing down their
|
// This allows swapping cameras' positions without tearing down their
|
||||||
@ -168,18 +171,24 @@ const Multiview = (props: MultiviewProps) => {
|
|||||||
// When no camera is selected, use the index within selected. (Actually,
|
// When no camera is selected, use the index within selected. (Actually,
|
||||||
// -1 minus the index, to disambiguate between the two cases.)
|
// -1 minus the index, to disambiguate between the two cases.)
|
||||||
const key = e ?? -1 - i;
|
const key = e ?? -1 - i;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Monoview
|
<Monoview
|
||||||
key={key}
|
key={key}
|
||||||
cameras={props.cameras}
|
cameras={props.cameras}
|
||||||
cameraIndex={e}
|
cameraIndex={e}
|
||||||
renderCamera={props.renderCamera}
|
renderCamera={props.renderCamera}
|
||||||
onSelect={(cameraIndex) =>
|
onSelect={(cameraIndex) => {
|
||||||
updateSelected({ selectedIndex: i, cameraIndex })
|
updateSelected({selectedIndex: i, cameraIndex})
|
||||||
}
|
searchParams.set('cams', JSON.stringify(selectedReducer(selected, {selectedIndex: i, cameraIndex})))
|
||||||
|
setSearchParams(searchParams)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root} ref={outerRef}>
|
<div className={classes.root} ref={outerRef}>
|
||||||
<div className="mid">
|
<div className="mid">
|
||||||
@ -200,24 +209,17 @@ interface MonoviewProps {
|
|||||||
|
|
||||||
/** A single pane of a Multiview, including its camera chooser. */
|
/** A single pane of a Multiview, including its camera chooser. */
|
||||||
const Monoview = (props: MonoviewProps) => {
|
const Monoview = (props: MonoviewProps) => {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const handleChange = (event: SelectChangeEvent<number | null>) => {
|
const handleChange = (event: SelectChangeEvent<number | null>) => {
|
||||||
const {
|
const {
|
||||||
target: { value },
|
target: { value },
|
||||||
} = event;
|
} = event;
|
||||||
if (value !== null && value !== undefined) {
|
|
||||||
setSearchParams(new URLSearchParams({cam: value.toString()}));
|
|
||||||
} else {
|
|
||||||
setSearchParams(new URLSearchParams({ }))
|
|
||||||
}
|
|
||||||
|
|
||||||
props.onSelect(typeof value === "string" ? parseInt(value) : value);
|
props.onSelect(typeof value === "string" ? parseInt(value) : value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromQueryIndexOrNull = searchParams.has('cam') ? Number.parseInt(searchParams.get('cam') as string, 10) : null;
|
|
||||||
const chooser = (
|
const chooser = (
|
||||||
<Select
|
<Select
|
||||||
value={props.cameraIndex == null ? fromQueryIndexOrNull: props.cameraIndex}
|
value={props.cameraIndex == null ? null: props.cameraIndex}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
size="small"
|
size="small"
|
||||||
@ -238,7 +240,7 @@ const Monoview = (props: MonoviewProps) => {
|
|||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
return props.renderCamera(
|
return props.renderCamera(
|
||||||
props.cameraIndex === null ? fromQueryIndexOrNull === null ? null : props.cameras[fromQueryIndexOrNull] : props.cameras[props.cameraIndex],
|
props.cameraIndex === null ? null : props.cameras[props.cameraIndex],
|
||||||
chooser
|
chooser
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,7 @@ import ErrorBoundary from "./ErrorBoundary";
|
|||||||
import { SnackbarProvider } from "./snackbars";
|
import { SnackbarProvider } from "./snackbars";
|
||||||
import AdapterDateFns from "@mui/lab/AdapterDateFns";
|
import AdapterDateFns from "@mui/lab/AdapterDateFns";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
@ -34,7 +35,9 @@ ReactDOM.render(
|
|||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||||
<SnackbarProvider autoHideDuration={5000}>
|
<SnackbarProvider autoHideDuration={5000}>
|
||||||
<App />
|
<BrowserRouter >
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
Loading…
Reference in New Issue
Block a user