Skip to content

Commit

Permalink
new-log-viewer: Add NotificationContextProvider for managing pop-up…
Browse files Browse the repository at this point in the history
… messages; add pop-ups for errors and remove status bar dummy message. (#84)
  • Loading branch information
junhaoliao authored Oct 19, 2024
1 parent ae24669 commit f402065
Show file tree
Hide file tree
Showing 13 changed files with 444 additions and 87 deletions.
13 changes: 8 additions & 5 deletions new-log-viewer/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Layout from "./components/Layout";
import NotificationContextProvider from "./contexts/NotificationContextProvider";
import StateContextProvider from "./contexts/StateContextProvider";
import UrlContextProvider from "./contexts/UrlContextProvider";

Expand All @@ -10,11 +11,13 @@ import UrlContextProvider from "./contexts/UrlContextProvider";
*/
const App = () => {
return (
<UrlContextProvider>
<StateContextProvider>
<Layout/>
</StateContextProvider>
</UrlContextProvider>
<NotificationContextProvider>
<UrlContextProvider>
<StateContextProvider>
<Layout/>
</StateContextProvider>
</UrlContextProvider>
</NotificationContextProvider>
);
};

Expand Down
4 changes: 3 additions & 1 deletion new-log-viewer/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {CssVarsProvider} from "@mui/joy/styles";
import {CssVarsProvider} from "@mui/joy";

import {CONFIG_KEY} from "../typings/config";
import {CONFIG_DEFAULT} from "../utils/config";
import CentralContainer from "./CentralContainer";
import MenuBar from "./MenuBar";
import PopUps from "./PopUps";
import StatusBar from "./StatusBar";
import APP_THEME from "./theme";

Expand All @@ -23,6 +24,7 @@ const Layout = () => {
<MenuBar/>
<CentralContainer/>
<StatusBar/>
<PopUps/>
</CssVarsProvider>
);
};
Expand Down
121 changes: 121 additions & 0 deletions new-log-viewer/src/components/PopUps/PopUpMessageBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {
useContext,
useEffect,
useRef,
useState,
} from "react";

import {
Alert,
Box,
CircularProgress,
IconButton,
Typography,
} from "@mui/joy";

import CloseIcon from "@mui/icons-material/Close";

import {
NotificationContext,
PopUpMessage,
} from "../../contexts/NotificationContextProvider";
import {WithId} from "../../typings/common";
import {LOG_LEVEL} from "../../typings/logs";
import {DO_NOT_TIMEOUT_VALUE} from "../../typings/notifications";


const AUTO_DISMISS_PERCENT_UPDATE_INTERVAL_MILLIS = 50;

interface PopUpMessageProps {
message: WithId<PopUpMessage>,
}

/**
* Display a pop-up message in an alert box.
*
* @param props
* @param props.message
* @return
*/
const PopUpMessageBox = ({message}: PopUpMessageProps) => {
const {id, level, message: messageStr, title, timeoutMillis} = message;

const {handlePopUpMessageClose} = useContext(NotificationContext);
const [percentRemaining, setPercentRemaining] = useState<number>(100);
const intervalCountRef = useRef<number>(0);

const handleCloseButtonClick = () => {
handlePopUpMessageClose(id);
};

useEffect(() => {
if (DO_NOT_TIMEOUT_VALUE === timeoutMillis) {
return () => {};
}

const totalIntervals = Math.ceil(
timeoutMillis / AUTO_DISMISS_PERCENT_UPDATE_INTERVAL_MILLIS
);
const intervalId = setInterval(() => {
intervalCountRef.current++;
const newPercentRemaining = 100 - (100 * (intervalCountRef.current / totalIntervals));
if (0 >= newPercentRemaining) {
handlePopUpMessageClose(id);
}
setPercentRemaining(newPercentRemaining);
}, AUTO_DISMISS_PERCENT_UPDATE_INTERVAL_MILLIS);

return () => {
clearInterval(intervalId);
};
}, [
timeoutMillis,
handlePopUpMessageClose,
id,
]);

const color = level >= LOG_LEVEL.ERROR ?
"danger" :
"primary";

return (
<Alert
className={"pop-up-message-box-alert"}
color={color}
variant={"outlined"}
>
<div className={"pop-up-message-box-alert-layout"}>
<Box className={"pop-up-message-box-title-container"}>
<Typography
className={"pop-up-message-box-title-text"}
color={color}
level={"title-md"}
>
{title}
</Typography>
<CircularProgress
color={color}
determinate={true}
size={"sm"}
thickness={3}
value={percentRemaining}
>
<IconButton
className={"pop-up-message-box-close-button"}
color={color}
size={"sm"}
onClick={handleCloseButtonClick}
>
<CloseIcon/>
</IconButton>
</CircularProgress>
</Box>
<Typography level={"body-sm"}>
{messageStr}
</Typography>
</div>
</Alert>
);
};

export default PopUpMessageBox;
47 changes: 47 additions & 0 deletions new-log-viewer/src/components/PopUps/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.pop-up-messages-container-snackbar {
/* Disable pointer events on the transparent container to allow components underneath to be
accessed. */
pointer-events: none;

right: 14px !important;
bottom: var(--ylv-status-bar-height) !important;

padding: 0 !important;

background: transparent !important;
border: none !important;
box-shadow: none !important;
}

.pop-up-messages-container-stack {
scrollbar-width: none;
overflow-y: auto;
height: calc(100vh - var(--ylv-status-bar-height) - var(--ylv-menu-bar-height));
}

.pop-up-message-box-alert {
/* Restore pointer events on the pop-up messages. See above `pointer-events: none` in
`.pop-up-messages-container-snackbar`. */
pointer-events: initial;
padding-inline: 18px !important;
}

.pop-up-message-box-alert-layout {
width: 300px;
}

.pop-up-message-box-title-container {
display: flex;
align-items: center;
}

.pop-up-message-box-title-text {
flex-grow: 1;
}

.pop-up-message-box-close-button {
/* stylelint-disable-next-line custom-property-pattern */
--IconButton-size: 18px !important;

border-radius: 18px !important;
}
42 changes: 42 additions & 0 deletions new-log-viewer/src/components/PopUps/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {useContext} from "react";

import {
Snackbar,
Stack,
} from "@mui/joy";

import {NotificationContext} from "../../contexts/NotificationContextProvider";
import PopUpMessageBox from "./PopUpMessageBox";

import "./index.css";


/**
* Displays pop-ups in a transparent container positioned on the right side of the viewport.
*
* @return
*/
const PopUps = () => {
const {popUpMessages} = useContext(NotificationContext);

return (
<Snackbar
className={"pop-up-messages-container-snackbar"}
open={0 < popUpMessages.length}
>
<Stack
className={"pop-up-messages-container-stack"}
direction={"column-reverse"}
gap={1}
>
{popUpMessages.map((message) => (
<PopUpMessageBox
key={message.id}
message={message}/>
))}
</Stack>
</Snackbar>
);
};

export default PopUps;
1 change: 0 additions & 1 deletion new-log-viewer/src/components/StatusBar/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@

.status-message {
flex-grow: 1;
padding-left: 8px;
}
7 changes: 2 additions & 5 deletions new-log-viewer/src/components/StatusBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@ const StatusBar = () => {

return (
<Sheet className={"status-bar"}>
<Typography
className={"status-message"}
level={"body-sm"}
>
Status message
<Typography className={"status-message"}>
{/* This is left blank intentionally until status messages are implemented. */}
</Typography>
<Button
color={"primary"}
Expand Down
Loading

0 comments on commit f402065

Please sign in to comment.