feat: moving notifications into separate component

This commit is contained in:
Mose Müller 2023-08-07 16:15:14 +02:00
parent a447dc2820
commit 6e8fa23a44
9 changed files with 98 additions and 74 deletions

View File

@ -1,12 +1,5 @@
import { useEffect, useReducer, useRef, useState } from 'react';
import {
Navbar,
Form,
Offcanvas,
Container,
Toast,
ToastContainer
} from 'react-bootstrap';
import { Navbar, Form, Offcanvas, Container } from 'react-bootstrap';
import { hostname, port, socket } from './socket';
import {
DataServiceComponent,
@ -14,6 +7,7 @@ import {
} from './components/DataServiceComponent';
import './App.css';
import { getDataServiceJSONValueByPathAndKey } from './utils/nestedObjectUtils';
import { Notifications } from './components/NotificationsComponent';
type ValueType = boolean | string | number | object;
@ -21,10 +15,10 @@ type State = DataServiceJSON | null;
type Action =
| { type: 'SET_DATA'; data: DataServiceJSON }
| { type: 'UPDATE_ATTRIBUTE'; parent_path: string; name: string; value: ValueType };
type UpdateNotification = {
type UpdateMessage = {
data: { parent_path: string; name: string; value: object };
};
type ExceptionNotification = {
type ExceptionMessage = {
data: { exception: string; type: string };
};
@ -132,10 +126,14 @@ const App = () => {
);
};
const removeExceptionById = (id: number) => {
setExceptions((prevNotifications) => prevNotifications.filter((n) => n.id !== id));
};
const handleCloseSettings = () => setShowSettings(false);
const handleShowSettings = () => setShowSettings(true);
function onNotify(value: UpdateNotification) {
function onNotify(value: UpdateMessage) {
// Extracting data from the notification
const { parent_path, name, value: newValue } = value.data;
@ -170,12 +168,11 @@ const App = () => {
setNotifications((prevNotifications) => [newNotification, ...prevNotifications]);
}
function onException(value: ExceptionNotification) {
function onException(value: ExceptionMessage) {
const currentTime = new Date();
const timeString = currentTime.toISOString().substr(11, 8);
const newNotification = {
type: 'exception',
id: Math.random(),
time: timeString,
text: `${value.data.type}: ${value.data.exception}.`
@ -216,59 +213,13 @@ const App = () => {
</Container>
</Navbar>
<ToastContainer
className="navbarOffset toastContainer"
position="top-end"
style={{ position: 'fixed' }}>
{showNotification &&
notifications.map((notification) => (
<Toast
className="notificationToast"
key={notification.id}
onClose={() => {
removeNotificationById(notification.id);
}}
onClick={() => {
removeNotificationById(notification.id);
}}
onMouseLeave={() => {
// For exception type notifications, do not dismiss on mouse leave
if (notification.type !== 'exception') {
removeNotificationById(notification.id);
}
}}
show={true}
autohide={true}
delay={2000}>
<Toast.Header
closeButton={notification.type === 'exception'}
className={`${'notificationToast'} text-right`}>
<strong className="me-auto">Notification</strong>
<small>{notification.time}</small>
</Toast.Header>
<Toast.Body>{notification.text}</Toast.Body>
</Toast>
))}
{exceptions.map((exception) => (
// Always render exceptions, regardless of showNotification
<Toast
className="exceptionToast"
key={exception.id}
onClose={() => {
setExceptions((prevExceptions) =>
prevExceptions.filter((e) => e.id !== exception.id)
);
}}
show={true}
autohide={false}>
<Toast.Header closeButton className="exceptionToast text-right">
<strong className="me-auto">Exception</strong>
<small>{exception.time}</small>
</Toast.Header>
<Toast.Body>{exception.text}</Toast.Body>
</Toast>
))}
</ToastContainer>
<Notifications
showNotification={showNotification}
notifications={notifications}
exceptions={exceptions}
removeNotificationById={removeNotificationById}
removeExceptionById={removeExceptionById}
/>
<Offcanvas
show={showSettings}

View File

@ -0,0 +1,73 @@
import React from 'react';
import { ToastContainer, Toast } from 'react-bootstrap';
type Notification = {
id: number;
time: string;
text: string;
};
type NotificationProps = {
showNotification: boolean;
notifications: Notification[];
exceptions: Notification[];
removeNotificationById: (id: number) => void;
removeExceptionById: (id: number) => void;
};
export const Notifications = React.memo((props: NotificationProps) => {
const {
showNotification,
notifications,
exceptions,
removeExceptionById,
removeNotificationById
} = props;
return (
<ToastContainer
className="navbarOffset toastContainer"
position="top-end"
style={{ position: 'fixed' }}>
{showNotification &&
notifications.map((notification) => (
<Toast
className="notificationToast"
key={notification.id}
onClose={() => removeNotificationById(notification.id)}
onClick={() => {
removeNotificationById(notification.id);
}}
onMouseLeave={() => {
removeNotificationById(notification.id);
}}
show={true}
autohide={true}
delay={2000}>
<Toast.Header closeButton={false} className="notificationToast text-right">
<strong className="me-auto">Notification</strong>
<small>{notification.time}</small>
</Toast.Header>
<Toast.Body>{notification.text}</Toast.Body>
</Toast>
))}
{exceptions.map((exception) => (
<Toast
className="exceptionToast"
key={exception.id}
onClose={() => removeExceptionById(exception.id)}
onClick={() => {
removeExceptionById(exception.id);
}}
show={true}
autohide={false}>
<Toast.Header closeButton className="exceptionToast text-right">
<strong className="me-auto">Exception</strong>
<small>{exception.time}</small>
</Toast.Header>
<Toast.Body>{exception.text}</Toast.Body>
</Toast>
))}
</ToastContainer>
);
});

View File

@ -1,13 +1,13 @@
{
"files": {
"main.css": "/static/css/main.d5ec2545.css",
"main.js": "/static/js/main.0683ca07.js",
"main.js": "/static/js/main.0eb7a5df.js",
"index.html": "/index.html",
"main.d5ec2545.css.map": "/static/css/main.d5ec2545.css.map",
"main.0683ca07.js.map": "/static/js/main.0683ca07.js.map"
"main.0eb7a5df.js.map": "/static/js/main.0eb7a5df.js.map"
},
"entrypoints": [
"static/css/main.d5ec2545.css",
"static/js/main.0683ca07.js"
"static/js/main.0eb7a5df.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site displaying a pydase UI."/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>pydase App</title><script defer="defer" src="/static/js/main.0683ca07.js"></script><link href="/static/css/main.d5ec2545.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site displaying a pydase UI."/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>pydase App</title><script defer="defer" src="/static/js/main.0eb7a5df.js"></script><link href="/static/css/main.d5ec2545.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long