import { useCallback, useEffect, useReducer, useState } from 'react'; import { Navbar, Form, Offcanvas, Container } from 'react-bootstrap'; import { hostname, port, socket } from './socket'; import './App.css'; import { Notifications, Notification, LevelName } from './components/NotificationsComponent'; import { ConnectionToast } from './components/ConnectionToast'; import { setNestedValueByPath, State } from './utils/stateUtils'; import { WebSettingsContext, WebSetting } from './WebSettings'; import { SerializedValue, GenericComponent } from './components/GenericComponent'; type Action = | { type: 'SET_DATA'; data: State } | { type: 'UPDATE_ATTRIBUTE'; fullAccessPath: string; newValue: SerializedValue; }; type UpdateMessage = { data: { full_access_path: string; value: SerializedValue }; }; type LogMessage = { levelname: LevelName; message: string; }; const reducer = (state: State, action: Action): State => { switch (action.type) { case 'SET_DATA': return action.data; case 'UPDATE_ATTRIBUTE': { if (state === null) { return null; } return { ...state, value: setNestedValueByPath(state.value, action.fullAccessPath, action.newValue) }; } default: throw new Error(); } }; const App = () => { const [state, dispatch] = useReducer(reducer, null); const [webSettings, setWebSettings] = useState>({}); const [isInstantUpdate, setIsInstantUpdate] = useState(false); const [showSettings, setShowSettings] = useState(false); const [showNotification, setShowNotification] = useState(false); const [notifications, setNotifications] = useState([]); const [connectionStatus, setConnectionStatus] = useState('connecting'); useEffect(() => { // Allow the user to add a custom css file fetch(`http://${hostname}:${port}/custom.css`) .then((response) => { if (response.ok) { // If the file exists, create a link element for the custom CSS const link = document.createElement('link'); link.href = `http://${hostname}:${port}/custom.css`; link.type = 'text/css'; link.rel = 'stylesheet'; document.head.appendChild(link); } }) .catch(console.error); // Handle the error appropriately socket.on('connect', () => { // Fetch data from the API when the client connects fetch(`http://${hostname}:${port}/service-properties`) .then((response) => response.json()) .then((data: State) => dispatch({ type: 'SET_DATA', data })); fetch(`http://${hostname}:${port}/web-settings`) .then((response) => response.json()) .then((data: Record) => setWebSettings(data)); setConnectionStatus('connected'); }); socket.on('disconnect', () => { setConnectionStatus('disconnected'); setTimeout(() => { // Only set "reconnecting" is the state is still "disconnected" // E.g. when the client has already reconnected setConnectionStatus((currentState) => currentState === 'disconnected' ? 'reconnecting' : currentState ); }, 2000); }); socket.on('notify', onNotify); socket.on('log', onLogMessage); return () => { socket.off('notify', onNotify); socket.off('log', onLogMessage); }; }, []); // Adding useCallback to prevent notify to change causing a re-render of all // components const addNotification = useCallback( (message: string, levelname: LevelName = 'DEBUG') => { // Getting the current time in the required format const timeStamp = new Date().toISOString().substring(11, 19); // Adding an id to the notification to provide a way of removing it const id = Math.random(); // Custom logic for notifications setNotifications((prevNotifications) => [ { levelname, id, message, timeStamp }, ...prevNotifications ]); }, [] ); const removeNotificationById = (id: number) => { setNotifications((prevNotifications) => prevNotifications.filter((n) => n.id !== id) ); }; const handleCloseSettings = () => setShowSettings(false); const handleShowSettings = () => setShowSettings(true); function onNotify(value: UpdateMessage) { // Extracting data from the notification const { full_access_path: fullAccessPath, value: newValue } = value.data; // Dispatching the update to the reducer dispatch({ type: 'UPDATE_ATTRIBUTE', fullAccessPath, newValue }); } function onLogMessage(value: LogMessage) { addNotification(value.message, value.levelname); } // While the data is loading if (!state) { return ; } return ( <> Data Service App Settings setIsInstantUpdate(e.target.checked)} type="switch" label="Enable Instant Update" /> setShowNotification(e.target.checked)} type="switch" label="Show Notifications" /> {/* Add any additional controls you want here */}
); }; export default App;