Skip to content

Commit

Permalink
Lookout UI: dark mode and visual improvements (#4107)
Browse files Browse the repository at this point in the history
Add dark mode to the Lookout UI and make other visual improvements:

- add syntax highlighting and correct the consistency for displaying code blocks in various languages
- make job sets table more responsive and use MUI table component for better styling consistency
- remove the use of react-virtualized, which is no longer well-maintained (last published two years ago). I don't think a virtualised table is necessary here given the volume of rows we're likely to display - all the job sets in a queue.
  • Loading branch information
mauriceyap authored Dec 20, 2024
1 parent 6b15e67 commit a57e7f2
Show file tree
Hide file tree
Showing 36 changed files with 641 additions and 1,344 deletions.
12 changes: 5 additions & 7 deletions internal/lookout/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,22 @@
"@mui/icons-material": "^6.1.10",
"@mui/lab": "^6.0.0-beta.18",
"@mui/material": "^6.1.10",
"@re-dev/react-truncate": "^0.4.3",
"@tanstack/react-query": "^5.62.3",
"@tanstack/react-table": "^8.7.0",
"@visx/mock-data": "^1.0.0",
"@visx/stats": "^1.4.0",
"@visx/visx": "^1.4.0",
"date-fns": "^2.29.3",
"date-fns-tz": "^1.3.7",
"js-yaml": "^4.0.0",
"lodash": "^4.17.21",
"notistack": "^3.0.1",
"oidc-client-ts": "^2.3.0",
"prism-react-renderer": "^2.4.1",
"prismjs": "^1.29.0",
"qs": "^6.11.0",
"query-string": "^6.13.7",
"react": "^18",
"react-dom": "^18",
"react-router-dom": "6.14.2",
"react-truncate": "^2.4.0",
"react-virtualized": "^9.22.5",
"semver": "6.3.1",
"tough-cookie": "^4.1.3",
"use-debounce": "^10.0.0",
Expand Down Expand Up @@ -75,12 +74,11 @@
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.14",
"@types/js-yaml": "^4.0.0",
"@types/lodash": "^4.17.13",
"@types/node": "^22.10.2",
"@types/qs": "^6.9.17",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-truncate": "^2.3.7",
"@types/react-virtualized": "^9.22.0",
"@types/uuid": "^8.3.0",
"@types/validator": "^13.7.3",
"@typescript-eslint/eslint-plugin": "^8.18.0",
Expand Down
12 changes: 0 additions & 12 deletions internal/lookout/ui/src/App.css

This file was deleted.

24 changes: 18 additions & 6 deletions internal/lookout/ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react"

import { ThemeProvider } from "@mui/material"
import { CssBaseline, styled, ThemeProvider } from "@mui/material"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { SnackbarProvider } from "notistack"
import { UserManager, WebStorageStateStore, UserManagerSettings, User } from "oidc-client-ts"
Expand All @@ -14,7 +14,18 @@ import { Services, ServicesProvider } from "./services/context"
import { theme } from "./theme/theme"
import { CommandSpec, OidcConfig, withRouter } from "./utils"

import "./App.css"
const AppContainer = styled("div")({
display: "flex",
flexDirection: "column",
height: "100vh",
})

const AppContent = styled("div")({
height: "100%",
minHeight: 0,
display: "flex",
flexDirection: "column",
})

export const queryClient = new QueryClient({
defaultOptions: {
Expand Down Expand Up @@ -135,15 +146,16 @@ export function App(props: AppProps): JSX.Element {

const result = (
<ThemeProvider theme={theme} defaultMode="light">
<CssBaseline />
<SnackbarProvider anchorOrigin={{ horizontal: "right", vertical: "bottom" }} autoHideDuration={8000} maxSnack={3}>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<UserManagerProvider value={userManager}>
<AuthWrapper userManager={userManager} isAuthenticated={isAuthenticated}>
<ServicesProvider services={props.services}>
<div className="app-container">
<AppContainer>
<NavBar customTitle={props.customTitle} username={username} />
<div className="app-content">
<AppContent>
<Routes>
<Route
path="/"
Expand Down Expand Up @@ -183,8 +195,8 @@ export function App(props: AppProps): JSX.Element {
}
/>
</Routes>
</div>
</div>
</AppContent>
</AppContainer>
</ServicesProvider>
</AuthWrapper>
</UserManagerProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { AddFilter } from "./icons"
const OuterContainer = styled("div")({
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: "0.5ch",
})

const StyledIconButton = styled(IconButton)<IconButtonProps & { hidden: boolean }>(({ hidden }) => ({
padding: 0,
visibility: hidden ? "hidden" : "unset",
}))

Expand Down
28 changes: 0 additions & 28 deletions internal/lookout/ui/src/components/CheckboxHeaderRow.tsx

This file was deleted.

67 changes: 0 additions & 67 deletions internal/lookout/ui/src/components/CheckboxRow.tsx

This file was deleted.

148 changes: 148 additions & 0 deletions internal/lookout/ui/src/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { useCallback } from "react"

import { Download } from "@mui/icons-material"
import { IconButton, styled, useColorScheme } from "@mui/material"
import { Highlight, themes } from "prism-react-renderer"
import Prism from "prismjs"
import "prismjs/components/prism-bash"
import "prismjs/components/prism-yaml"

import { CopyIconButton } from "./CopyIconButton"

// All langauges in this set must be imported from Prism in the form:
// import "prismjs/components/prism-{language}"
type SupportedLanguage = "bash" | "yaml"

const DARK_PRISM_THEME = themes.oneDark
const LIGHT_PRISM_THEME = themes.oneLight

const CodeActionsContainer = styled("div")({
display: "flex",
position: "absolute",
top: 10,
right: 10,
opacity: 0,
transition: "opacity 300ms ease-in-out",
})

const CodeBlockContainer = styled("div")({
position: "relative",

"&:hover > .codeActionsContainer": {
opacity: 1,
},
})

const StyledPre = styled("pre")(({ theme }) => ({
lineHeight: 1.2,
fontSize: theme.typography.body2.fontSize,
overflow: "auto",
padding: 5,
borderRadius: 5,
minHeight: 50,
display: "flex",
alignItems: "center",
}))

const Code = styled("code")({
display: "table",
wordWrap: "break-word",
})

const CodeLine = styled("div")({
display: "table-row",
counterIncrement: "line-count",
})

const CodeLineNumber = styled("span")({
display: "table-cell",
textAlign: "right",
width: "1%",
position: "sticky",
left: 0,
padding: "0 12px",
overflowWrap: "normal",

"&::before": {
content: "counter(line-count)",
opacity: 0.4,
},
})

export type CodeBlockProps = {
language: SupportedLanguage | "text"
code: string
showLineNumbers: boolean
} & (
| {
downloadable: true
downloadBlobType: string
downloadFileName: string
}
| { downloadable: false; downloadBlobType?: undefined | string; downloadFileName?: undefined | string }
)

export const CodeBlock = ({
language,
code,
showLineNumbers,
downloadable,
downloadBlobType,
downloadFileName,
}: CodeBlockProps) => {
const { colorScheme } = useColorScheme()

const downloadFile = useCallback(() => {
if (!downloadable) {
return
}

const element = document.createElement("a")
const file = new Blob([code], {
type: downloadBlobType,
})
element.href = URL.createObjectURL(file)
element.download = downloadFileName
document.body.appendChild(element)
element.click()
}, [code, downloadable, downloadBlobType, downloadFileName])

return (
<CodeBlockContainer>
<CodeActionsContainer className="codeActionsContainer">
<CopyIconButton size="small" content={code} />
{downloadable && (
<IconButton size="small" title={`Download as ${language} file`} onClick={downloadFile}>
<Download />
</IconButton>
)}
</CodeActionsContainer>
<Highlight
prism={Prism}
theme={colorScheme === "dark" ? DARK_PRISM_THEME : LIGHT_PRISM_THEME}
language={language}
code={code}
>
{({ style, tokens, getLineProps, getTokenProps }) => (
<StyledPre style={style}>
<Code>
{tokens.map((line, i) => {
const lineTokens = line.map((token, key) => <span key={key} {...getTokenProps({ token })} />)
return showLineNumbers ? (
<CodeLine key={i} {...getLineProps({ line })}>
<CodeLineNumber />
<span>{lineTokens}</span>
</CodeLine>
) : (
<div key={i} {...getLineProps({ line })}>
{lineTokens}
</div>
)
})}
</Code>
</StyledPre>
)}
</Highlight>
</CodeBlockContainer>
)
}
1 change: 0 additions & 1 deletion internal/lookout/ui/src/components/CopyIconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { IconButton, IconButtonProps, styled, Tooltip } from "@mui/material"
const LEAVE_DELAY_MS = 1_000

const StyledIconButton = styled(IconButton)<IconButtonProps & { hidden: boolean }>(({ hidden }) => ({
padding: 0,
visibility: hidden ? "hidden" : "unset",
}))

Expand Down
Loading

0 comments on commit a57e7f2

Please sign in to comment.