diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0c4aa85..d20491a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,14 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useReducer } from 'react'; import { Component, ComponentLabel } from './components/component'; import { ButtonComponent } from './components/button'; import { socket } from './socket'; type AttributeType = 'str' | 'bool' | 'float' | 'int' | 'method' | 'Subclass'; +type ValueType = boolean | string | number | object; interface Attribute { type: AttributeType; - value?: any; + value?: ValueType; readonly: boolean; doc?: string | null; parameters?: Record; @@ -15,46 +16,104 @@ interface Attribute { } type MyData = Record; +type State = MyData | null; +type Action = + | { type: 'SET_DATA'; data: MyData } + | { type: 'UPDATE_ATTRIBUTE'; parent_path: string; name: string; value: ValueType }; +type NotificationElement = { + data: { parent_path: string; name: string; value: object }; +}; + +/** + * A function to update a specific property in a deeply nested object. + * The property to be updated is specified by a path array. + * + * @param {Array} path - An array where each element is a key in the object, + * forming a path to the property to be updated. + * @param {object} obj - The object to be updated. + * @param {object} value - The new value for the property specified by the path. + * @return {object} - A new object with the specified property updated. + */ +function updateNestedObject(path: Array, obj: object, value: ValueType) { + // Base case: If the path is empty, return the new value. + // This means we've reached the nested property to be updated. + if (path.length === 0) { + return value; + } + + // Recursive case: If the path is not empty, split it into the first key and the rest + // of the path. + const [first, ...rest] = path; + + // Return a new object that copies all properties of the original object, but updates + // the property specified by 'first'. + // The updated property is an object that copies all properties of the original nested + // object, but updates the 'value' property. + // The new 'value' property is the result of a recursive call to updateNestedObject, + // with the rest of the path, the value of the nested object as the object to be + // updated, and the new value. + return { + ...obj, + [first]: { + ...obj[first], + value: updateNestedObject(rest, obj[first]?.value || {}, value) + } + }; +} + +const reducer = (state: State, action: Action): State => { + switch (action.type) { + case 'SET_DATA': + return action.data; + case 'UPDATE_ATTRIBUTE': + if (!state) { + console.log('No state is set'); + return state; + } + { + const path = action.parent_path.split('.').slice(1).concat(action.name); + console.log(path); + + return updateNestedObject(path, state, action.value); + } + default: + throw new Error(); + } +}; const App = () => { - const [data, setData] = useState(null); - const [isConnected, setIsConnected] = useState(socket.connected); + const [state, dispatch] = useReducer(reducer, null); + // const [isConnected, setIsConnected] = useState(socket.connected); useEffect(() => { // Fetch data from the API when the component mounts fetch('http://localhost:8001/service-properties') .then((response) => response.json()) - .then(setData); - function onConnect() { - setIsConnected(true); + .then((data: MyData) => dispatch({ type: 'SET_DATA', data })); + + function onNotify(value: NotificationElement) { + dispatch({ + type: 'UPDATE_ATTRIBUTE', + parent_path: value.data.parent_path, + name: value.data.name, + value: value.data.value + }); } - function onDisconnect() { - setIsConnected(false); - } - - function onNotify(value: Record) { - console.log(value); - } - - socket.on('connect', onConnect); - socket.on('disconnect', onDisconnect); socket.on('notify', onNotify); return () => { - socket.off('connect', onConnect); - socket.off('disconnect', onDisconnect); socket.off('notify', onNotify); }; }, []); // While the data is loading - if (!data) { + if (!state) { return

Loading...

; } return (
- {Object.entries(data).map(([key, value]) => { + {Object.entries(state).map(([key, value]) => { if (value.type === 'bool') { return (
@@ -62,7 +121,7 @@ const App = () => { name={key} docString={value.doc} readOnly={value.readonly} - value={value.value} + value={Boolean(value.value)} />
);