Skip to content

Commit

Permalink
Partially replace Semantic UI React with MUI.
Browse files Browse the repository at this point in the history
  • Loading branch information
fniessink committed Sep 17, 2024
1 parent 8005a68 commit 993df86
Show file tree
Hide file tree
Showing 24 changed files with 641 additions and 370 deletions.
530 changes: 470 additions & 60 deletions components/frontend/package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion components/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
"private": true,
"proxy": "http://127.0.0.1:5001",
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^6.1.0",
"@mui/material": "^6.1.0",
"crypto-js": "^4.2.0",
"fomantic-ui-css": "^2.9.3",
"history": "^5.3.0",
Expand All @@ -18,7 +22,6 @@
"react-timeago": "^7.2.0",
"react-toastify": "^10.0.5",
"semantic-ui-react": "^2.1.5",
"use-local-storage-state": "^19.4.0",
"victory": "^37.1.1"
},
"scripts": {
Expand Down
51 changes: 30 additions & 21 deletions components/frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "react-toastify/dist/ReactToastify.css"
import "./App.css"

import { createTheme, ThemeProvider } from "@mui/material/styles"
import { Action } from "history"
import history from "history/browser"
import { Component } from "react"
Expand All @@ -14,6 +15,12 @@ import { registeredURLSearchParams } from "./hooks/url_search_query"
import { isValidDate_YYYYMMDD, toISODateStringInCurrentTZ } from "./utils"
import { showConnectionMessage, showMessage } from "./widgets/toast"

const theme = createTheme({
colorSchemes: {
dark: true, // Add a dark theme (light theme is available by default)
},
})

class App extends Component {
constructor(props) {
super(props)
Expand Down Expand Up @@ -237,27 +244,29 @@ class App extends Component {

render() {
return (
<AppUI
changed_fields={this.changed_fields}
dataModel={this.state.dataModel}
email={this.state.email}
openReportsOverview={() => this.openReportsOverview()}
handleDateChange={(date) => this.handleDateChange(date)}
key={this.state.report_uuid} // Make sure the AppUI is refreshed whenever the current report changes
lastUpdate={this.state.lastUpdate}
loading={this.state.loading}
nrMeasurements={this.state.nrMeasurements}
openReport={(report_uuid) => this.openReport(report_uuid)}
reload={(json) => this.reload(json)}
report_date={this.state.report_date}
report_uuid={this.state.report_uuid}
reports={this.state.reports}
reports_overview={this.state.reports_overview}
set_user={(username, email, sessionExpirationDateTime) =>
this.setUserSession(username, email, sessionExpirationDateTime)
}
user={this.state.user}
/>
<ThemeProvider theme={theme}>
<AppUI
changed_fields={this.changed_fields}
dataModel={this.state.dataModel}
email={this.state.email}
openReportsOverview={() => this.openReportsOverview()}
handleDateChange={(date) => this.handleDateChange(date)}
key={this.state.report_uuid} // Make sure the AppUI is refreshed whenever the current report changes
lastUpdate={this.state.lastUpdate}
loading={this.state.loading}
nrMeasurements={this.state.nrMeasurements}
openReport={(report_uuid) => this.openReport(report_uuid)}
reload={(json) => this.reload(json)}
report_date={this.state.report_date}
report_uuid={this.state.report_uuid}
reports={this.state.reports}
reports_overview={this.state.reports_overview}
set_user={(username, email, sessionExpirationDateTime) =>
this.setUserSession(username, email, sessionExpirationDateTime)
}
user={this.state.user}
/>
</ThemeProvider>
)
}
}
Expand Down
11 changes: 0 additions & 11 deletions components/frontend/src/App.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,6 @@ beforeAll(() => {
addEventListener: jest.fn(),
close: jest.fn(),
}))
Object.defineProperty(window, "matchMedia", {
value: jest.fn().mockImplementation((_query) => ({
matches: false,
addEventListener: () => {
/* No implementation needed */
},
removeEventListener: () => {
/* No implementation needed */
},
})),
})
})

beforeEach(() => {
Expand Down
24 changes: 5 additions & 19 deletions components/frontend/src/AppUI.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import "react-toastify/dist/ReactToastify.css"
import "./App.css"

import { useColorScheme } from "@mui/material/styles"
import { bool, func, number, object, string } from "prop-types"
import { useEffect } from "react"
import HashLinkObserver from "react-hash-link"
import { ToastContainer } from "react-toastify"
import useLocalStorageState from "use-local-storage-state"

import { useSettings } from "./app_ui_settings"
import { DarkMode } from "./context/DarkMode"
Expand Down Expand Up @@ -42,20 +41,7 @@ export function AppUI({
set_user,
user,
}) {
const [uiMode, setUIMode] = useLocalStorageState("ui_mode", { defaultValue: "follow_os" })
useEffect(() => {
const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)")
mediaQueryList.addEventListener("change", changeMode)
function changeMode(e) {
if (uiMode === "follow_os") {
// Only update if the user is following the OS mode setting
setUIMode(e.matches ? "dark" : "light") // Force redraw
setTimeout(() => setUIMode("follow_os")) // Reset setting
}
}
return () => mediaQueryList.removeEventListener("change", changeMode)
}, [uiMode, setUIMode])

const { mode, setMode } = useColorScheme()
const user_permissions = getUserPermissions(user, email, report_date, reports_overview.permissions || {})
const atReportsOverview = report_uuid === ""
const current_report = atReportsOverview ? null : reports.filter((report) => report.report_uuid === report_uuid)[0]
Expand All @@ -76,7 +62,7 @@ export function AppUI({
}
}

const darkMode = userPrefersDarkMode(uiMode)
const darkMode = userPrefersDarkMode(mode)
const backgroundColor = darkMode ? "rgb(40, 40, 40)" : "white"
return (
<div
Expand Down Expand Up @@ -107,8 +93,8 @@ export function AppUI({
/>
}
settings={settings}
setUIMode={setUIMode}
uiMode={uiMode}
setUIMode={setMode}
uiMode={mode}
/>
<ToastContainer theme="colored" />
<Permissions.Provider value={user_permissions}>
Expand Down
63 changes: 0 additions & 63 deletions components/frontend/src/AppUI.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,75 +50,12 @@ it("handles sorting", async () => {
expect(history.location.search).toEqual("")
})

let matchMediaMatches
let changeMode

beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
value: jest.fn().mockImplementation((_query) => {
return {
matches: matchMediaMatches,
addEventListener: (_eventType, eventHandler) => {
changeMode = eventHandler
},
removeEventListener: () => {
/* No implementation needed */
},
}
}),
})
})

async function renderAppUI() {
return await act(async () =>
render(<AppUI handleDateChange={jest.fn} report_uuid="" reports={[]} reports_overview={{}} />),
)
}

it("supports dark mode", async () => {
matchMediaMatches = true
const { container } = await renderAppUI()
expect(container.firstChild.style.background).toEqual("rgb(40, 40, 40)")
})

it("supports light mode", async () => {
matchMediaMatches = false
const { container } = await renderAppUI()
expect(container.firstChild.style.background).toEqual("white")
})

it("follows OS mode when switching to light mode", async () => {
matchMediaMatches = true
const { container } = await renderAppUI()
expect(container.firstChild.style.background).toEqual("rgb(40, 40, 40)")
act(() => {
changeMode({ matches: false })
})
expect(container.firstChild.style.background).toEqual("white")
})

it("follows OS mode when switching to dark mode", async () => {
matchMediaMatches = false
const { container } = await renderAppUI()
expect(container.firstChild.style.background).toEqual("white")
act(() => {
changeMode({ matches: true })
})
expect(container.firstChild.style.background).toEqual("rgb(40, 40, 40)")
})

it("ignores OS mode when mode explicitly set", async () => {
matchMediaMatches = false
const { container } = await act(async () => await renderAppUI())
fireEvent.click(screen.getByLabelText("Dark/light mode"))
fireEvent.click(screen.getByText("Light mode"))
expect(container.firstChild.style.background).toEqual("white")
act(() => {
changeMode({ matches: true })
})
expect(container.firstChild.style.background).toEqual("white")
})

it("resets all settings", async () => {
history.push("?date_interval=2")
await act(async () => await renderAppUI())
Expand Down
46 changes: 46 additions & 0 deletions components/frontend/src/dashboard/DashboardCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Card, CardActionArea, CardContent, CardHeader } from "@mui/material"
import { bool, element, func, oneOfType, string } from "prop-types"

import { childrenPropType } from "../sharedPropTypes"

export function DashboardCard({ children, onClick, selected, title, titleFirst }) {
const color = selected ? "info.main" : null
const header = (
<CardHeader
title={title}
// The selected class is for test purposes only
titleTypographyProps={{ className: selected ? "selected" : "", noWrap: true, variant: "h6" }}
sx={{
textAlign: "center",
verticalAlign: "center",
padding: "0px",
paddingTop: titleFirst ? "10px" : "0px",
color: color,
}}
/>
)
// The components below get a height of 100% to make sure they fill the available space of their container
return (
<Card
elevation={2}
onClick={onClick}
sx={{ border: 1, borderColor: color, height: "100%", "&:hover": { boxShadow: "4" } }}
variant="elevation"
>
<CardActionArea disableRipple={!onClick} sx={{ height: "100%" }}>
<CardContent sx={{ paddingBottom: titleFirst ? "0px" : "10px", paddingTop: "0px", height: "100%" }}>
{titleFirst && header}
{children}
{!titleFirst && header}
</CardContent>
</CardActionArea>
</Card>
)
}
DashboardCard.propTypes = {
children: childrenPropType,
onClick: func,
selected: bool,
title: oneOfType([element, string]),
titleFirst: bool,
}
24 changes: 0 additions & 24 deletions components/frontend/src/dashboard/FilterCard.js

This file was deleted.

16 changes: 0 additions & 16 deletions components/frontend/src/dashboard/FilterCardWithTable.css

This file was deleted.

21 changes: 7 additions & 14 deletions components/frontend/src/dashboard/FilterCardWithTable.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import "./FilterCardWithTable.css"

import { bool, func, string } from "prop-types"

import { Card, Header, Table } from "../semantic_ui_react_wrappers"
import { Table } from "../semantic_ui_react_wrappers"
import { childrenPropType } from "../sharedPropTypes"
import { FilterCard } from "./FilterCard"
import { DashboardCard } from "./DashboardCard"

export function FilterCardWithTable({ children, onClick, selected, title }) {
return (
<FilterCard onClick={onClick} selected={selected}>
<Card.Content>
<Header as="h3" className={selected ? "selected" : null} textAlign="center">
{title}
</Header>
<Table basic="very" compact="very" size="small">
<Table.Body>{children}</Table.Body>
</Table>
</Card.Content>
</FilterCard>
<DashboardCard onClick={onClick} selected={selected} title={title} titleFirst={true}>
<Table basic="very" compact="very" size="small">
<Table.Body>{children}</Table.Body>
</Table>
</DashboardCard>
)
}
FilterCardWithTable.propTypes = {
Expand Down
14 changes: 0 additions & 14 deletions components/frontend/src/dashboard/LegendCard.css

This file was deleted.

Loading

0 comments on commit 993df86

Please sign in to comment.