mirror of
				https://github.com/scottlamb/moonfire-nvr.git
				synced 2025-10-29 15:55:01 -04: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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user