mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-06-07 22:10:41 +02:00
feat: moving notifications into separate component
This commit is contained in:
parent
a447dc2820
commit
6e8fa23a44
@ -1,12 +1,5 @@
|
|||||||
import { useEffect, useReducer, useRef, useState } from 'react';
|
import { useEffect, useReducer, useRef, useState } from 'react';
|
||||||
import {
|
import { Navbar, Form, Offcanvas, Container } from 'react-bootstrap';
|
||||||
Navbar,
|
|
||||||
Form,
|
|
||||||
Offcanvas,
|
|
||||||
Container,
|
|
||||||
Toast,
|
|
||||||
ToastContainer
|
|
||||||
} from 'react-bootstrap';
|
|
||||||
import { hostname, port, socket } from './socket';
|
import { hostname, port, socket } from './socket';
|
||||||
import {
|
import {
|
||||||
DataServiceComponent,
|
DataServiceComponent,
|
||||||
@ -14,6 +7,7 @@ import {
|
|||||||
} from './components/DataServiceComponent';
|
} from './components/DataServiceComponent';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { getDataServiceJSONValueByPathAndKey } from './utils/nestedObjectUtils';
|
import { getDataServiceJSONValueByPathAndKey } from './utils/nestedObjectUtils';
|
||||||
|
import { Notifications } from './components/NotificationsComponent';
|
||||||
|
|
||||||
type ValueType = boolean | string | number | object;
|
type ValueType = boolean | string | number | object;
|
||||||
|
|
||||||
@ -21,10 +15,10 @@ type State = DataServiceJSON | null;
|
|||||||
type Action =
|
type Action =
|
||||||
| { type: 'SET_DATA'; data: DataServiceJSON }
|
| { type: 'SET_DATA'; data: DataServiceJSON }
|
||||||
| { type: 'UPDATE_ATTRIBUTE'; parent_path: string; name: string; value: ValueType };
|
| { type: 'UPDATE_ATTRIBUTE'; parent_path: string; name: string; value: ValueType };
|
||||||
type UpdateNotification = {
|
type UpdateMessage = {
|
||||||
data: { parent_path: string; name: string; value: object };
|
data: { parent_path: string; name: string; value: object };
|
||||||
};
|
};
|
||||||
type ExceptionNotification = {
|
type ExceptionMessage = {
|
||||||
data: { exception: string; type: string };
|
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 handleCloseSettings = () => setShowSettings(false);
|
||||||
const handleShowSettings = () => setShowSettings(true);
|
const handleShowSettings = () => setShowSettings(true);
|
||||||
|
|
||||||
function onNotify(value: UpdateNotification) {
|
function onNotify(value: UpdateMessage) {
|
||||||
// Extracting data from the notification
|
// Extracting data from the notification
|
||||||
const { parent_path, name, value: newValue } = value.data;
|
const { parent_path, name, value: newValue } = value.data;
|
||||||
|
|
||||||
@ -170,12 +168,11 @@ const App = () => {
|
|||||||
setNotifications((prevNotifications) => [newNotification, ...prevNotifications]);
|
setNotifications((prevNotifications) => [newNotification, ...prevNotifications]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onException(value: ExceptionNotification) {
|
function onException(value: ExceptionMessage) {
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
const timeString = currentTime.toISOString().substr(11, 8);
|
const timeString = currentTime.toISOString().substr(11, 8);
|
||||||
|
|
||||||
const newNotification = {
|
const newNotification = {
|
||||||
type: 'exception',
|
|
||||||
id: Math.random(),
|
id: Math.random(),
|
||||||
time: timeString,
|
time: timeString,
|
||||||
text: `${value.data.type}: ${value.data.exception}.`
|
text: `${value.data.type}: ${value.data.exception}.`
|
||||||
@ -216,59 +213,13 @@ const App = () => {
|
|||||||
</Container>
|
</Container>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<ToastContainer
|
<Notifications
|
||||||
className="navbarOffset toastContainer"
|
showNotification={showNotification}
|
||||||
position="top-end"
|
notifications={notifications}
|
||||||
style={{ position: 'fixed' }}>
|
exceptions={exceptions}
|
||||||
{showNotification &&
|
removeNotificationById={removeNotificationById}
|
||||||
notifications.map((notification) => (
|
removeExceptionById={removeExceptionById}
|
||||||
<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>
|
|
||||||
|
|
||||||
<Offcanvas
|
<Offcanvas
|
||||||
show={showSettings}
|
show={showSettings}
|
||||||
|
73
frontend/src/components/NotificationsComponent.tsx
Normal file
73
frontend/src/components/NotificationsComponent.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
});
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.d5ec2545.css",
|
"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",
|
"index.html": "/index.html",
|
||||||
"main.d5ec2545.css.map": "/static/css/main.d5ec2545.css.map",
|
"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": [
|
"entrypoints": [
|
||||||
"static/css/main.d5ec2545.css",
|
"static/css/main.d5ec2545.css",
|
||||||
"static/js/main.0683ca07.js"
|
"static/js/main.0eb7a5df.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -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
3
src/pydase/frontend/static/js/main.0eb7a5df.js
Normal file
3
src/pydase/frontend/static/js/main.0eb7a5df.js
Normal file
File diff suppressed because one or more lines are too long
1
src/pydase/frontend/static/js/main.0eb7a5df.js.map
Normal file
1
src/pydase/frontend/static/js/main.0eb7a5df.js.map
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user