mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-02-03 01:46:01 -05:00
Stop using deprecated @mui/styles
I was struggling to upgrade the version of mui stuff (material and date picker). I'm hoping getting rid of the deprecated stuff eases this a bit. I don't love that I can't just use sx on plain HTML stuff and have to wrap it in Box, but oh well. Looks like I'm not alone, fwiw. https://github.com/mui/material-ui/issues/23220
This commit is contained in:
parent
ae502200c0
commit
8d716bf4dd
21045
ui/package-lock.json
generated
21045
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,6 @@
|
||||
"@mui/icons-material": "^5.5.1",
|
||||
"@mui/lab": "^5.0.0-alpha.73",
|
||||
"@mui/material": "^5.5.1",
|
||||
"@mui/styles": "^5.5.1",
|
||||
"@react-hook/resize-observer": "^1.2.5",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/node": "^17.0.21",
|
||||
|
@ -6,25 +6,14 @@ import Button from "@mui/material/Button";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { createStyles, makeStyles } from "@mui/styles";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import AccountCircle from "@mui/icons-material/AccountCircle";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import React from "react";
|
||||
import { LoginState } from "./App";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
title: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
activity: {
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
})
|
||||
);
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
interface Props {
|
||||
loginState: LoginState;
|
||||
@ -36,7 +25,7 @@ interface Props {
|
||||
|
||||
// https://material-ui.com/components/app-bar/
|
||||
function MoonfireMenu(props: Props) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const [accountMenuAnchor, setAccountMenuAnchor] =
|
||||
React.useState<null | HTMLElement>(null);
|
||||
|
||||
@ -66,11 +55,13 @@ function MoonfireMenu(props: Props) {
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" className={classes.title}>
|
||||
<Typography variant="h6" sx={{ flexGrow: 1 }}>
|
||||
Moonfire NVR
|
||||
</Typography>
|
||||
{props.activityMenuPart !== null && (
|
||||
<div className={classes.activity}>{props.activityMenuPart}</div>
|
||||
<Box sx={{ marginRight: theme.spacing(2) }}>
|
||||
{props.activityMenuPart}
|
||||
</Box>
|
||||
)}
|
||||
{props.loginState !== "unknown" && props.loginState !== "logged-in" && (
|
||||
<Button color="inherit" onClick={props.requestLogin}>
|
||||
|
@ -2,11 +2,11 @@
|
||||
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
||||
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import { Camera, Stream, StreamType } from "../types";
|
||||
import Checkbox from "@mui/material/Checkbox";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { ToplevelResponse } from "../api";
|
||||
|
||||
interface Props {
|
||||
@ -15,30 +15,9 @@ interface Props {
|
||||
setSelected: (selected: Set<number>) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles({
|
||||
streamSelectorTable: {
|
||||
fontSize: "0.9rem",
|
||||
"& td:first-child": {
|
||||
paddingRight: "3px",
|
||||
},
|
||||
"& td:not(:first-child)": {
|
||||
textAlign: "center",
|
||||
},
|
||||
},
|
||||
check: {
|
||||
padding: "3px",
|
||||
},
|
||||
"@media (pointer: fine)": {
|
||||
check: {
|
||||
padding: "0px",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** Returns a table which allows selecting zero or more streams. */
|
||||
const StreamMultiSelector = ({ toplevel, selected, setSelected }: Props) => {
|
||||
const theme = useTheme();
|
||||
const classes = useStyles();
|
||||
const setStream = (s: Stream, checked: boolean) => {
|
||||
const updated = new Set(selected);
|
||||
if (checked) {
|
||||
@ -92,13 +71,10 @@ const StreamMultiSelector = ({ toplevel, selected, setSelected }: Props) => {
|
||||
function checkbox(st: StreamType) {
|
||||
const s = c.streams[st];
|
||||
if (s === undefined) {
|
||||
return (
|
||||
<Checkbox className={classes.check} color="secondary" disabled />
|
||||
);
|
||||
return <Checkbox color="secondary" disabled />;
|
||||
}
|
||||
return (
|
||||
<Checkbox
|
||||
className={classes.check}
|
||||
size="small"
|
||||
checked={selected.has(s.id)}
|
||||
color="secondary"
|
||||
@ -120,7 +96,26 @@ const StreamMultiSelector = ({ toplevel, selected, setSelected }: Props) => {
|
||||
padding: theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
<table className={classes.streamSelectorTable}>
|
||||
<Box
|
||||
component="table"
|
||||
sx={{
|
||||
fontSize: "0.9rem",
|
||||
"& td:first-child": {
|
||||
paddingRight: "3px",
|
||||
},
|
||||
"& td:not(:first-child)": {
|
||||
textAlign: "center",
|
||||
},
|
||||
"& .MuiCheckbox-root": {
|
||||
padding: "3px",
|
||||
},
|
||||
"@media (pointer: fine)": {
|
||||
"& .MuiCheckbox-root": {
|
||||
padding: "0px",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<td />
|
||||
@ -129,7 +124,7 @@ const StreamMultiSelector = ({ toplevel, selected, setSelected }: Props) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{cameraRows}</tbody>
|
||||
</table>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
@ -5,8 +5,7 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import utcToZonedTime from "date-fns-tz/utcToZonedTime";
|
||||
@ -26,56 +25,6 @@ import { FrameProps } from "../App";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import FilterList from "@mui/icons-material/FilterList";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
selectors: {
|
||||
width: "max-content",
|
||||
"& .MuiCard-root": {
|
||||
marginRight: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
videoTable: {
|
||||
flexGrow: 1,
|
||||
width: "max-content",
|
||||
height: "max-content",
|
||||
"& .streamHeader": {
|
||||
background: theme.palette.primary.light,
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
"& .MuiTableBody-root:not(:last-child):after": {
|
||||
content: "''",
|
||||
display: "block",
|
||||
height: theme.spacing(2),
|
||||
},
|
||||
"& tbody .recording": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
"& .opt": {
|
||||
[theme.breakpoints.down("lg")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// When there's a video modal up, make the content as large as possible
|
||||
// without distorting it. Center it in the screen and ensure that the video
|
||||
// element only takes up the space actually used by the content, so that
|
||||
// clicking outside it will dismiss the modal.
|
||||
videoModal: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
"& video": {
|
||||
objectFit: "fill",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
interface FullScreenVideoProps {
|
||||
src: string;
|
||||
aspect: [number, number];
|
||||
@ -225,7 +174,7 @@ const calcSelectedStreams = (
|
||||
};
|
||||
|
||||
const Main = ({ toplevel, timeZoneName, Frame }: Props) => {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
selectedStreamIds,
|
||||
@ -288,7 +237,31 @@ const Main = ({ toplevel, timeZoneName, Frame }: Props) => {
|
||||
setActiveRecording(null);
|
||||
};
|
||||
const recordingsTable = (
|
||||
<TableContainer component={Paper} className={classes.videoTable}>
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
width: "max-content",
|
||||
height: "max-content",
|
||||
"& .streamHeader": {
|
||||
background: theme.palette.primary.light,
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
"& .MuiTableBody-root:not(:last-child):after": {
|
||||
content: "''",
|
||||
display: "block",
|
||||
height: theme.spacing(2),
|
||||
},
|
||||
"& tbody .recording": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
"& .opt": {
|
||||
[theme.breakpoints.down("lg")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Table size="small">{videoLists}</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
@ -305,10 +278,22 @@ const Main = ({ toplevel, timeZoneName, Frame }: Props) => {
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<div className={classes.root}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
margin: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className={classes.selectors}
|
||||
sx={{ display: showSelectors ? "block" : "none" }}
|
||||
sx={{
|
||||
display: showSelectors ? "block" : "none",
|
||||
width: "max-content",
|
||||
"& .MuiCard-root": {
|
||||
marginRight: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<StreamMultiSelector
|
||||
toplevel={toplevel}
|
||||
@ -331,7 +316,22 @@ const Main = ({ toplevel, timeZoneName, Frame }: Props) => {
|
||||
</Box>
|
||||
{videoLists.length > 0 && recordingsTable}
|
||||
{activeRecording != null && (
|
||||
<Modal open onClose={closeModal} className={classes.videoModal}>
|
||||
<Modal
|
||||
open
|
||||
onClose={closeModal}
|
||||
sx={{
|
||||
// Make the content as large as possible without distorting it.
|
||||
// Center it in the screen and ensure that the video element only
|
||||
// takes up the space actually used by the content, so that clicking
|
||||
// outside it will dismiss the modal.
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
"& video": {
|
||||
objectFit: "fill",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FullScreenVideo
|
||||
src={api.recordingUrl(
|
||||
activeRecording[0].camera.uuid,
|
||||
@ -347,7 +347,7 @@ const Main = ({ toplevel, timeZoneName, Frame }: Props) => {
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
</Frame>
|
||||
);
|
||||
};
|
||||
|
@ -2,12 +2,12 @@
|
||||
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
||||
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import React, { useReducer } from "react";
|
||||
import { Camera } from "../types";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
export interface Layout {
|
||||
@ -24,52 +24,6 @@ const LAYOUTS: Layout[] = [
|
||||
];
|
||||
const MAX_CAMERAS = 9;
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
flex: "1 0 0",
|
||||
color: "white",
|
||||
marginTop: theme.spacing(2),
|
||||
overflow: "hidden",
|
||||
|
||||
// TODO: this mid-level div can probably be removed.
|
||||
"& .mid": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
display: "inline-block",
|
||||
},
|
||||
},
|
||||
inner: {
|
||||
// match parent's size without influencing it.
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
backgroundColor: "#000",
|
||||
overflow: "hidden",
|
||||
display: "grid",
|
||||
gridGap: "0px",
|
||||
|
||||
// These class names must match LAYOUTS (above).
|
||||
"&.solo": {
|
||||
gridTemplateColumns: "100%",
|
||||
gridTemplateRows: "100%",
|
||||
},
|
||||
"&.two-by-two": {
|
||||
gridTemplateColumns: "repeat(2, calc(100% / 2))",
|
||||
gridTemplateRows: "repeat(2, calc(100% / 2))",
|
||||
},
|
||||
"&.main-plus-five, &.three-by-three": {
|
||||
gridTemplateColumns: "repeat(3, calc(100% / 3))",
|
||||
gridTemplateRows: "repeat(3, calc(100% / 3))",
|
||||
},
|
||||
"&.main-plus-five > div:nth-child(1)": {
|
||||
gridColumn: "span 2",
|
||||
gridRow: "span 2",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export interface MultiviewProps {
|
||||
cameras: Camera[];
|
||||
layoutIndex: number;
|
||||
@ -152,6 +106,7 @@ function selectedReducer(old: SelectedCameras, op: SelectOp): SelectedCameras {
|
||||
*/
|
||||
const Multiview = (props: MultiviewProps) => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const theme = useTheme();
|
||||
|
||||
const [selected, updateSelected] = useReducer(
|
||||
selectedReducer,
|
||||
@ -161,7 +116,6 @@ const Multiview = (props: MultiviewProps) => {
|
||||
);
|
||||
|
||||
const outerRef = React.useRef<HTMLDivElement>(null);
|
||||
const classes = useStyles();
|
||||
const layout = LAYOUTS[props.layoutIndex];
|
||||
|
||||
const monoviews = selected.slice(0, layout.cameras).map((e, i) => {
|
||||
@ -194,13 +148,60 @@ const Multiview = (props: MultiviewProps) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classes.root} ref={outerRef}>
|
||||
<Box
|
||||
ref={outerRef}
|
||||
sx={{
|
||||
flex: "1 0 0",
|
||||
color: "white",
|
||||
marginTop: theme.spacing(2),
|
||||
overflow: "hidden",
|
||||
|
||||
// TODO: this mid-level div can probably be removed.
|
||||
"& > .mid": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
display: "inline-block",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="mid">
|
||||
<div className={`${classes.inner} ${layout.className}`}>
|
||||
<Box
|
||||
className={layout.className}
|
||||
sx={{
|
||||
// match parent's size without influencing it.
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
backgroundColor: "#000",
|
||||
overflow: "hidden",
|
||||
display: "grid",
|
||||
gridGap: "0px",
|
||||
|
||||
// These class names must match LAYOUTS (above).
|
||||
"&.solo": {
|
||||
gridTemplateColumns: "100%",
|
||||
gridTemplateRows: "100%",
|
||||
},
|
||||
"&.two-by-two": {
|
||||
gridTemplateColumns: "repeat(2, calc(100% / 2))",
|
||||
gridTemplateRows: "repeat(2, calc(100% / 2))",
|
||||
},
|
||||
"&.main-plus-five, &.three-by-three": {
|
||||
gridTemplateColumns: "repeat(3, calc(100% / 3))",
|
||||
gridTemplateRows: "repeat(3, calc(100% / 3))",
|
||||
},
|
||||
"&.main-plus-five > div:nth-child(1)": {
|
||||
gridColumn: "span 2",
|
||||
gridRow: "span 2",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{monoviews}
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -8,8 +8,7 @@ import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import FormHelperText from "@mui/material/FormHelperText";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
@ -17,12 +16,6 @@ import React, { useEffect } from "react";
|
||||
import * as api from "./api";
|
||||
import { useSnackbars } from "./snackbars";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
avatar: {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
},
|
||||
}));
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onSuccess: () => void;
|
||||
@ -55,7 +48,7 @@ interface Props {
|
||||
* <tt>--allow-unauthenticated-permissions</tt>), the caller may ignore this.
|
||||
*/
|
||||
const Login = ({ open, onSuccess, handleClose }: Props) => {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const snackbars = useSnackbars();
|
||||
|
||||
// This is a simple uncontrolled form; use refs.
|
||||
@ -119,7 +112,7 @@ const Login = ({ open, onSuccess, handleClose }: Props) => {
|
||||
fullWidth={true}
|
||||
>
|
||||
<DialogTitle id="login-title">
|
||||
<Avatar className={classes.avatar}>
|
||||
<Avatar sx={{ backgroundColor: theme.palette.secondary.main }}>
|
||||
<LockOutlinedIcon />
|
||||
</Avatar>
|
||||
Log in
|
||||
|
Loading…
x
Reference in New Issue
Block a user