upgrade material-ui to latest beta

This is a surprisingly complex upgrade. Some relevant changes from
[their CHANGELOG](https://github.com/mui-org/material-ui/blob/v5.0.0-beta.3/CHANGELOG.md):

* @material-ui/core/styles no longer re-exports some stuff from
  @material-ui/styles
* there's no more defaultTheme, so tests need to provide one
* select's onChange has a new type; match that. I haven't actually
  tried that the string form (apparently from autofill) works correctly.
* checkboxes no longer default to the secondary color; explicitly
  request this in some places.
* checkbox no longer has a checked argument; use event.target.checked
  instead.
* date pickers have switched to the new style system, so I had to
  redo how I was overridding their spacing for desktop.
* LoadingButton now has a loading property, not pending
* createMuiTheme is no createTheme
This commit is contained in:
Scott Lamb 2021-08-10 11:57:16 -07:00
parent c55032dfcd
commit 39a63e03ae
15 changed files with 29918 additions and 3140 deletions

32790
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,21 +6,22 @@
"@emotion/react": "^11.1.5", "@emotion/react": "^11.1.5",
"@emotion/styled": "^11.1.5", "@emotion/styled": "^11.1.5",
"@fontsource/roboto": "^4.2.1", "@fontsource/roboto": "^4.2.1",
"@material-ui/core": "^5.0.0-alpha.26", "@material-ui/core": "^5.0.0-beta.3",
"@material-ui/icons": "^5.0.0-alpha.26", "@material-ui/icons": "^5.0.0-beta.1",
"@material-ui/lab": "^5.0.0-alpha.26", "@material-ui/lab": "^5.0.0-alpha.42",
"@material-ui/styles": "^5.0.0-beta.3",
"@react-hook/resize-observer": "^1.2.0", "@react-hook/resize-observer": "^1.2.0",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/node": "^14.14.22", "@types/node": "^16.3.1",
"@types/react": "^17.0.0", "@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0", "@types/react-dom": "^17.0.0",
"date-fns": "^2.18.0", "date-fns": "^2.18.0",
"date-fns-tz": "^1.1.3", "date-fns-tz": "^1.1.3",
"gzipper": "^4.4.0", "gzipper": "^5.0.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-scripts": "4.0.1", "react-scripts": "^4.0.3",
"typescript": "^4.1.3" "typescript": "^4.3.5"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
@ -64,10 +65,10 @@
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.11.4", "@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.6.3", "@testing-library/user-event": "^12.8.3",
"http-proxy-middleware": "^1.0.6", "http-proxy-middleware": "^2.0.1",
"msw": "^0.26.1", "msw": "^0.26.2",
"prettier": "2.2.1" "prettier": "^2.2.1"
} }
} }

View File

@ -6,7 +6,8 @@ import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import Menu from "@material-ui/core/Menu"; import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem"; import MenuItem from "@material-ui/core/MenuItem";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import { Theme } from "@material-ui/core/styles";
import { createStyles, makeStyles } from "@material-ui/styles";
import Toolbar from "@material-ui/core/Toolbar"; import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import AccountCircle from "@material-ui/icons/AccountCircle"; import AccountCircle from "@material-ui/icons/AccountCircle";
@ -38,10 +39,8 @@ interface Props {
// https://material-ui.com/components/app-bar/ // https://material-ui.com/components/app-bar/
function MoonfireMenu(props: Props) { function MoonfireMenu(props: Props) {
const classes = useStyles(); const classes = useStyles();
const [ const [accountMenuAnchor, setAccountMenuAnchor] =
accountMenuAnchor, React.useState<null | HTMLElement>(null);
setAccountMenuAnchor,
] = React.useState<null | HTMLElement>(null);
const handleMenu = (event: React.MouseEvent<HTMLElement>) => { const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAccountMenuAnchor(event.currentTarget); setAccountMenuAnchor(event.currentTarget);
@ -94,7 +93,6 @@ function MoonfireMenu(props: Props) {
</IconButton> </IconButton>
<Menu <Menu
anchorEl={accountMenuAnchor} anchorEl={accountMenuAnchor}
getContentAnchorEl={null}
keepMounted keepMounted
anchorOrigin={{ anchorOrigin={{
vertical: "bottom", vertical: "bottom",

View File

@ -4,12 +4,6 @@
import Avatar from "@material-ui/core/Avatar"; import Avatar from "@material-ui/core/Avatar";
import Container from "@material-ui/core/Container"; import Container from "@material-ui/core/Container";
import {
createStyles,
Theme,
WithStyles,
withStyles,
} from "@material-ui/core/styles";
import BugReportIcon from "@material-ui/icons/BugReport"; import BugReportIcon from "@material-ui/icons/BugReport";
import React from "react"; import React from "react";
@ -17,16 +11,7 @@ interface State {
error: any; error: any;
} }
const styles = (theme: Theme) => interface Props {
createStyles({
avatar: {
backgroundColor: theme.palette.secondary.main,
float: "left",
marginRight: "1em",
},
});
interface Props extends WithStyles<typeof styles> {
children: React.ReactNode; children: React.ReactNode;
} }
@ -55,7 +40,7 @@ class MoonfireErrorBoundary extends React.Component<Props, State> {
} }
render() { render() {
const { classes, children } = this.props; const { children } = this.props;
if (this.state.error !== null) { if (this.state.error !== null) {
var error; var error;
@ -74,7 +59,13 @@ class MoonfireErrorBoundary extends React.Component<Props, State> {
return ( return (
<Container> <Container>
<Avatar className={classes.avatar}> <Avatar
sx={{
float: "left",
bgcolor: "secondary.main",
marginRight: "1em",
}}
>
<BugReportIcon color="primary" /> <BugReportIcon color="primary" />
</Avatar> </Avatar>
<h1>Error</h1> <h1>Error</h1>
@ -130,4 +121,4 @@ class MoonfireErrorBoundary extends React.Component<Props, State> {
} }
} }
export default withStyles(styles)(MoonfireErrorBoundary); export default MoonfireErrorBoundary;

View File

@ -53,7 +53,13 @@ const DisplaySelector = (props: Props) => {
id="split90k" id="split90k"
size="small" size="small"
value={props.split90k} value={props.split90k}
onChange={(e) => props.setSplit90k(e.target.value)} onChange={(e) =>
props.setSplit90k(
typeof e.target.value === "string"
? parseInt(e.target.value)
: e.target.value
)
}
displayEmpty displayEmpty
> >
{DURATIONS.map(([l, d]) => ( {DURATIONS.map(([l, d]) => (
@ -72,10 +78,9 @@ const DisplaySelector = (props: Props) => {
<Checkbox <Checkbox
checked={props.trimStartAndEnd} checked={props.trimStartAndEnd}
size="small" size="small"
onChange={(_, checked: boolean) => onChange={(event) => props.setTrimStartAndEnd(event.target.checked)}
props.setTrimStartAndEnd(checked)
}
name="trim-start-and-end" name="trim-start-and-end"
color="secondary"
/> />
} }
label="Trim start and end" label="Trim start and end"
@ -87,8 +92,9 @@ const DisplaySelector = (props: Props) => {
<Checkbox <Checkbox
checked={props.timestampTrack} checked={props.timestampTrack}
size="small" size="small"
onChange={(_, checked: boolean) => props.setTimestampTrack(checked)} onChange={(event) => props.setTimestampTrack(event.target.checked)}
name="timestamp-track" name="timestamp-track"
color="secondary"
/> />
} }
label="Timestamp track" label="Timestamp track"

View File

@ -5,7 +5,8 @@
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import { Camera, Stream, StreamType } from "../types"; import { Camera, Stream, StreamType } from "../types";
import Checkbox from "@material-ui/core/Checkbox"; import Checkbox from "@material-ui/core/Checkbox";
import { makeStyles, useTheme } from "@material-ui/core/styles"; import { useTheme } from "@material-ui/core/styles";
import { makeStyles } from "@material-ui/styles";
interface Props { interface Props {
cameras: Camera[]; cameras: Camera[];
@ -86,14 +87,17 @@ const StreamMultiSelector = ({ cameras, selected, setSelected }: Props) => {
function checkbox(st: StreamType) { function checkbox(st: StreamType) {
const s = c.streams[st]; const s = c.streams[st];
if (s === undefined) { if (s === undefined) {
return <Checkbox className={classes.check} disabled />; return (
<Checkbox className={classes.check} color="secondary" disabled />
);
} }
return ( return (
<Checkbox <Checkbox
className={classes.check} className={classes.check}
size="small" size="small"
checked={selected.has(s)} checked={selected.has(s)}
onChange={(_, checked: boolean) => setStream(s, checked)} color="secondary"
onChange={(event) => setStream(s, event.target.checked)}
/> />
); );
} }

View File

@ -3,7 +3,9 @@
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception // SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
import { Stream } from "../types"; import { Stream } from "../types";
import StaticDatePicker from "@material-ui/lab/StaticDatePicker"; import StaticDatePicker, {
StaticDatePickerProps,
} from "@material-ui/lab/StaticDatePicker";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { zonedTimeToUtc } from "date-fns-tz"; import { zonedTimeToUtc } from "date-fns-tz";
import { addDays, addMilliseconds, differenceInMilliseconds } from "date-fns"; import { addDays, addMilliseconds, differenceInMilliseconds } from "date-fns";
@ -17,6 +19,7 @@ import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup"; import RadioGroup from "@material-ui/core/RadioGroup";
import TimePicker, { TimePickerProps } from "@material-ui/lab/TimePicker"; import TimePicker, { TimePickerProps } from "@material-ui/lab/TimePicker";
import Collapse from "@material-ui/core/Collapse"; import Collapse from "@material-ui/core/Collapse";
import Box from "@material-ui/core/Box";
interface Props { interface Props {
selectedStreams: Set<Stream>; selectedStreams: Set<Stream>;
@ -39,6 +42,47 @@ const MyTimePicker = (
/> />
); );
const SmallStaticDatePicker = (props: StaticDatePickerProps<Date>) => {
// The spacing defined at https://material.io/components/date-pickers#specs
// seems plenty big enough (on desktop). Not sure why material-ui wants
// to make it bigger but that doesn't work well with our layout.
// This adjustment is a fragile hack but seems to work for now.
const DATE_SIZE = 32;
return (
<Box
sx={{
"@media (pointer: fine)": {
"& > div": {
minWidth: 256,
},
"& > div > div, & > div > div > div, & .MuiCalendarPicker-root": {
width: 256,
},
"& .MuiTypography-caption": {
width: DATE_SIZE,
margin: 0,
},
"& .PrivatePickersSlideTransition-root": {
minHeight: DATE_SIZE * 6,
},
'& .PrivatePickersSlideTransition-root [role="row"]': {
margin: 0,
},
"& .MuiPickersDay-dayWithMargin": {
margin: 0,
},
"& .MuiPickersDay-root": {
width: DATE_SIZE,
height: DATE_SIZE,
},
},
}}
>
<StaticDatePicker {...props} />
</Box>
);
};
/** /**
* Combines the date-part of <tt>dayMillis</tt> and the time part of * Combines the date-part of <tt>dayMillis</tt> and the time part of
* <tt>time</tt>. If <tt>time</tt> is null, assume it reaches the end of the * <tt>time</tt>. If <tt>time</tt> is null, assume it reaches the end of the
@ -260,7 +304,7 @@ const TimerangeSelector = ({
<Card sx={{ padding: theme.spacing(1) }}> <Card sx={{ padding: theme.spacing(1) }}>
<div> <div>
<FormLabel component="legend">From</FormLabel> <FormLabel component="legend">From</FormLabel>
<StaticDatePicker <SmallStaticDatePicker
displayStaticWrapperAs="desktop" displayStaticWrapperAs="desktop"
value={startDate} value={startDate}
shouldDisableDate={shouldDisableDate} shouldDisableDate={shouldDisableDate}
@ -299,17 +343,17 @@ const TimerangeSelector = ({
> >
<FormControlLabel <FormControlLabel
value="same-day" value="same-day"
control={<Radio size="small" />} control={<Radio size="small" color="secondary" />}
label="Same day" label="Same day"
/> />
<FormControlLabel <FormControlLabel
value="other-day" value="other-day"
control={<Radio size="small" />} control={<Radio size="small" color="secondary" />}
label="Other day" label="Other day"
/> />
</RadioGroup> </RadioGroup>
<Collapse in={days.endType === "other-day"}> <Collapse in={days.endType === "other-day"}>
<StaticDatePicker <SmallStaticDatePicker
displayStaticWrapperAs="desktop" displayStaticWrapperAs="desktop"
value={endDate} value={endDate}
shouldDisableDate={(d: Date | null) => shouldDisableDate={(d: Date | null) =>

View File

@ -5,7 +5,8 @@
import Box from "@material-ui/core/Box"; import Box from "@material-ui/core/Box";
import Modal from "@material-ui/core/Modal"; import Modal from "@material-ui/core/Modal";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import { makeStyles, Theme } from "@material-ui/core/styles"; import { Theme } from "@material-ui/core/styles";
import { makeStyles } from "@material-ui/styles";
import Table from "@material-ui/core/Table"; import Table from "@material-ui/core/Table";
import TableContainer from "@material-ui/core/TableContainer"; import TableContainer from "@material-ui/core/TableContainer";
import utcToZonedTime from "date-fns-tz/utcToZonedTime"; import utcToZonedTime from "date-fns-tz/utcToZonedTime";

View File

@ -2,12 +2,13 @@
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. // 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 // SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
import Select from "@material-ui/core/Select"; import Select, { SelectChangeEvent } from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem"; import MenuItem from "@material-ui/core/MenuItem";
import React, { useReducer, useState } from "react"; import React, { useReducer, useState } from "react";
import { Camera } from "../types"; import { Camera } from "../types";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/styles";
import useResizeObserver from "@react-hook/resize-observer"; import useResizeObserver from "@react-hook/resize-observer";
import { Theme } from "@material-ui/core/styles";
export interface Layout { export interface Layout {
className: string; className: string;
@ -23,7 +24,7 @@ const LAYOUTS: Layout[] = [
]; ];
const MAX_CAMERAS = 9; const MAX_CAMERAS = 9;
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme: Theme) => ({
root: { root: {
flex: "1 0 0", flex: "1 0 0",
color: "white", color: "white",
@ -98,7 +99,13 @@ export const MultiviewChooser = (props: MultiviewChooserProps) => {
<Select <Select
id="layout" id="layout"
value={props.layoutIndex} value={props.layoutIndex}
onChange={(e) => props.onChoice(e.target.value)} onChange={(e) =>
props.onChoice(
typeof e.target.value === "string"
? parseInt(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.
@ -238,10 +245,16 @@ 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 handleChange = (event: SelectChangeEvent<number | null>) => {
const {
target: { value },
} = event;
props.onSelect(typeof value === "string" ? parseInt(value) : value);
};
const chooser = ( const chooser = (
<Select <Select
value={props.cameraIndex == null ? undefined : props.cameraIndex} value={props.cameraIndex == null ? undefined : props.cameraIndex}
onChange={(e) => props.onSelect(e.target.value ?? null)} onChange={handleChange}
displayEmpty displayEmpty
size="small" size="small"
sx={{ sx={{

View File

@ -38,8 +38,8 @@ afterEach(() => {
}); });
test("success", async () => { test("success", async () => {
const handleClose = jest.fn(); const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn(); const onSuccess = jest.fn().mockName("handleOpen");
renderWithCtx( renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} /> <Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
); );
@ -54,8 +54,8 @@ test("success", async () => {
// so the delay("infinite") request just sticks around, even though the fetch // so the delay("infinite") request just sticks around, even though the fetch
// has been aborted. Maybe https://github.com/mswjs/msw/pull/585 will fix it. // has been aborted. Maybe https://github.com/mswjs/msw/pull/585 will fix it.
xtest("close while pending", async () => { xtest("close while pending", async () => {
const handleClose = jest.fn(); const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn(); const onSuccess = jest.fn().mockName("handleOpen");
const { rerender } = renderWithCtx( const { rerender } = renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} /> <Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
); );
@ -73,8 +73,8 @@ xtest("close while pending", async () => {
}); });
test("bad credentials", async () => { test("bad credentials", async () => {
const handleClose = jest.fn(); const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn(); const onSuccess = jest.fn().mockName("handleOpen");
renderWithCtx( renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} /> <Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
); );
@ -85,8 +85,8 @@ test("bad credentials", async () => {
}); });
test("server error", async () => { test("server error", async () => {
const handleClose = jest.fn(); const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn(); const onSuccess = jest.fn().mockName("handleOpen");
renderWithCtx( renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} /> <Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
); );
@ -100,8 +100,8 @@ test("server error", async () => {
}); });
test("network error", async () => { test("network error", async () => {
const handleClose = jest.fn(); const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn(); const onSuccess = jest.fn().mockName("handleOpen");
renderWithCtx( renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} /> <Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
); );

View File

@ -8,7 +8,8 @@ import DialogActions from "@material-ui/core/DialogActions";
import DialogTitle from "@material-ui/core/DialogTitle"; import DialogTitle from "@material-ui/core/DialogTitle";
import FormControl from "@material-ui/core/FormControl"; import FormControl from "@material-ui/core/FormControl";
import FormHelperText from "@material-ui/core/FormHelperText"; import FormHelperText from "@material-ui/core/FormHelperText";
import { makeStyles, Theme } from "@material-ui/core/styles"; import { Theme } from "@material-ui/core/styles";
import { makeStyles } from "@material-ui/styles";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import LockOutlinedIcon from "@material-ui/icons/LockOutlined"; import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
import LoadingButton from "@material-ui/lab/LoadingButton"; import LoadingButton from "@material-ui/lab/LoadingButton";
@ -62,15 +63,15 @@ const Login = ({ open, onSuccess, handleClose }: Props) => {
const passwordRef = React.useRef<HTMLInputElement>(null); const passwordRef = React.useRef<HTMLInputElement>(null);
const [error, setError] = React.useState<string | null>(null); const [error, setError] = React.useState<string | null>(null);
const [pending, setPending] = React.useState<api.LoginRequest | null>(null); const [loading, setLoading] = React.useState<api.LoginRequest | null>(null);
useEffect(() => { useEffect(() => {
if (pending === null) { if (loading === null) {
return; return;
} }
let abort = new AbortController(); let abort = new AbortController();
const send = async (signal: AbortSignal) => { const send = async (signal: AbortSignal) => {
let response = await api.login(pending, { signal }); let response = await api.login(loading, { signal });
switch (response.status) { switch (response.status) {
case "aborted": case "aborted":
break; break;
@ -83,10 +84,10 @@ const Login = ({ open, onSuccess, handleClose }: Props) => {
key: "login-error", key: "login-error",
}); });
} }
setPending(null); setLoading(null);
break; break;
case "success": case "success":
setPending(null); setLoading(null);
onSuccess(); onSuccess();
} }
}; };
@ -94,16 +95,16 @@ const Login = ({ open, onSuccess, handleClose }: Props) => {
return () => { return () => {
abort.abort(); abort.abort();
}; };
}, [pending, onSuccess, snackbars]); }, [loading, onSuccess, snackbars]);
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => { const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
// Suppress duplicate login attempts when latency is high. // Suppress duplicate login attempts when latency is high.
if (pending !== null) { if (loading !== null) {
return; return;
} }
setPending({ setLoading({
username: usernameRef.current!.value, username: usernameRef.current!.value,
password: passwordRef.current!.value, password: passwordRef.current!.value,
}); });
@ -154,7 +155,7 @@ const Login = ({ open, onSuccess, handleClose }: Props) => {
type="submit" type="submit"
variant="contained" variant="contained"
color="secondary" color="secondary"
pending={pending !== null} loading={loading !== null}
> >
Log in Log in
</LoadingButton> </LoadingButton>

View File

@ -10,48 +10,3 @@ body,
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
@media (pointer: fine) {
/*
* The spacing defined at https://material.io/components/date-pickers#specs
* seems plenty big enough (on desktop). Not sure why material-ui wants
* to make it bigger but that doesn't work well with our layout.
*
* Defining this here (in a global .css file) is the first way I could find
* to override properties of .MuiPickerStaticWrapper-root. It's unfortunately
* a _parent_ of the element that gets the <DatePicker>'s className applied,
* and it doesn't seem to be exposed for a global style override
* <https://next.material-ui.com/customization/theme-components/#global-style-overrides>.
*/
.MuiPickersStaticWrapper-root {
min-width: 256px !important;
}
/* Increased specificity here so it doesn't apply to the popup time picker. */
.MuiPickersStaticWrapper-root .MuiPickerView-root {
width: 256px !important;
}
.MuiPickersCalendar-root {
min-height: 160px !important;
}
.MuiPickersCalendar-weekDayLabel {
width: 32px !important;
height: 32px !important;
margin: 0 !important;
}
.MuiPickersCalendar-week {
margin: 0 !important;
}
.MuiPickersDay-dayWithMargin {
margin: 0 !important;
}
.MuiPickersDay-root {
width: 32px !important;
height: 32px !important;
}
}

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception // SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
import CssBaseline from "@material-ui/core/CssBaseline"; import CssBaseline from "@material-ui/core/CssBaseline";
import { ThemeProvider, createMuiTheme } from "@material-ui/core/styles"; import { ThemeProvider, createTheme } from "@material-ui/core/styles";
import StyledEngineProvider from "@material-ui/core/StyledEngineProvider"; import StyledEngineProvider from "@material-ui/core/StyledEngineProvider";
import LocalizationProvider from "@material-ui/lab/LocalizationProvider"; import LocalizationProvider from "@material-ui/lab/LocalizationProvider";
import "@fontsource/roboto"; import "@fontsource/roboto";
@ -15,7 +15,7 @@ import { SnackbarProvider } from "./snackbars";
import AdapterDateFns from "@material-ui/lab/AdapterDateFns"; import AdapterDateFns from "@material-ui/lab/AdapterDateFns";
import "./index.css"; import "./index.css";
const theme = createMuiTheme({ const theme = createTheme({
palette: { palette: {
primary: { primary: {
main: "#000000", main: "#000000",

View File

@ -93,7 +93,8 @@ const ctx = React.createContext<Snackbars | null>(null);
// and I couldn't figure out a way to do that with hooks. // and I couldn't figure out a way to do that with hooks.
export class SnackbarProvider export class SnackbarProvider
extends React.Component<SnackbarProviderProps, State> extends React.Component<SnackbarProviderProps, State>
implements Snackbars { implements Snackbars
{
constructor(props: SnackbarProviderProps) { constructor(props: SnackbarProviderProps) {
super(props); super(props);
this.state = { queue: [] }; this.state = { queue: [] };

View File

@ -2,6 +2,7 @@
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. // 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 // SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
import { createTheme, ThemeProvider } from "@material-ui/core/styles";
import { render } from "@testing-library/react"; import { render } from "@testing-library/react";
import { SnackbarProvider } from "./snackbars"; import { SnackbarProvider } from "./snackbars";
@ -10,7 +11,9 @@ export function renderWithCtx(
): Pick<ReturnType<typeof render>, "rerender"> { ): Pick<ReturnType<typeof render>, "rerender"> {
function wrapped(children: React.ReactElement): React.ReactElement { function wrapped(children: React.ReactElement): React.ReactElement {
return ( return (
<SnackbarProvider autoHideDuration={5000}>{children}</SnackbarProvider> <ThemeProvider theme={createTheme()}>
<SnackbarProvider autoHideDuration={5000}>{children}</SnackbarProvider>
</ThemeProvider>
); );
} }
const { rerender } = render(wrapped(children)); const { rerender } = render(wrapped(children));