mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-06-11 07:47:12 +02:00
feat: components implement their notifications now
- removing nestedObjectUtils and useNotification hook - passing addNotification method to all components - components can use the addNotification method to create their notifications
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { useEffect, useReducer, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
|
||||
import { Navbar, Form, Offcanvas, Container } from 'react-bootstrap';
|
||||
import { hostname, port, socket } from './socket';
|
||||
import {
|
||||
@ -6,9 +6,7 @@ import {
|
||||
DataServiceJSON
|
||||
} from './components/DataServiceComponent';
|
||||
import './App.css';
|
||||
import { getDataServiceJSONValueByPathAndKey } from './utils/nestedObjectUtils';
|
||||
import { Notifications } from './components/NotificationsComponent';
|
||||
import { useNotification } from './hooks/useNotification';
|
||||
|
||||
type ValueType = boolean | string | number | object;
|
||||
|
||||
@ -114,51 +112,11 @@ const reducer = (state: State, action: Action): State => {
|
||||
const App = () => {
|
||||
const [state, dispatch] = useReducer(reducer, null);
|
||||
const stateRef = useRef(state); // Declare a reference to hold the current state
|
||||
|
||||
const [isInstantUpdate, setIsInstantUpdate] = useState(true);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
const { notifications, notify, removeNotificationById } = useNotification();
|
||||
const {
|
||||
notifications: exceptions,
|
||||
notify: notifyException,
|
||||
removeNotificationById: removeExceptionById
|
||||
} = useNotification();
|
||||
|
||||
const handleCloseSettings = () => setShowSettings(false);
|
||||
const handleShowSettings = () => setShowSettings(true);
|
||||
|
||||
function onNotify(value: UpdateMessage) {
|
||||
// Extracting data from the notification
|
||||
const { parent_path: parentPath, name, value: newValue } = value.data;
|
||||
|
||||
// Dispatching the update to the reducer
|
||||
dispatch({
|
||||
type: 'UPDATE_ATTRIBUTE',
|
||||
parentPath,
|
||||
name,
|
||||
value: newValue
|
||||
});
|
||||
|
||||
// Formatting the value if it is of type 'Quantity'
|
||||
let notificationMsg: object | string = newValue;
|
||||
const path = parentPath.concat('.', name);
|
||||
if (
|
||||
getDataServiceJSONValueByPathAndKey(stateRef.current, path, 'type') === 'Quantity'
|
||||
) {
|
||||
notificationMsg = `${newValue['magnitude']} ${newValue['unit']}`;
|
||||
}
|
||||
|
||||
// Creating a new notification
|
||||
const newNotification = `${parentPath}.${name} changed to ${notificationMsg}.`;
|
||||
// Adding the new notification to the list
|
||||
notify(newNotification);
|
||||
}
|
||||
|
||||
function onException(value: ExceptionMessage) {
|
||||
const newNotification = `${value.data.type}: ${value.data.exception}.`;
|
||||
notifyException(newNotification);
|
||||
}
|
||||
const [showNotification, setShowNotification] = useState(true);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [exceptions, setExceptions] = useState([]);
|
||||
|
||||
// Keep the state reference up to date
|
||||
useEffect(() => {
|
||||
@ -180,6 +138,64 @@ const App = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Adding useCallback to prevent notify to change causing a re-render of all
|
||||
// components
|
||||
const addNotification = useCallback((text: string) => {
|
||||
// Getting the current time in the required format
|
||||
const timeString = 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) => [
|
||||
{ id, text, time: timeString },
|
||||
...prevNotifications
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const notifyException = (text: string) => {
|
||||
// Getting the current time in the required format
|
||||
const timeString = 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
|
||||
setExceptions((prevNotifications) => [
|
||||
{ id, text, time: timeString },
|
||||
...prevNotifications
|
||||
]);
|
||||
};
|
||||
const removeNotificationById = (id: number) => {
|
||||
setNotifications((prevNotifications) =>
|
||||
prevNotifications.filter((n) => n.id !== id)
|
||||
);
|
||||
};
|
||||
|
||||
const removeExceptionById = (id: number) => {
|
||||
setExceptions((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 { parent_path: parentPath, name, value: newValue } = value.data;
|
||||
|
||||
// Dispatching the update to the reducer
|
||||
dispatch({
|
||||
type: 'UPDATE_ATTRIBUTE',
|
||||
parentPath,
|
||||
name,
|
||||
value: newValue
|
||||
});
|
||||
}
|
||||
|
||||
function onException(value: ExceptionMessage) {
|
||||
const newException = `${value.data.type}: ${value.data.exception}.`;
|
||||
notifyException(newException);
|
||||
}
|
||||
|
||||
// While the data is loading
|
||||
if (!state) {
|
||||
return <p>Loading...</p>;
|
||||
@ -230,6 +246,7 @@ const App = () => {
|
||||
<DataServiceComponent
|
||||
props={state as DataServiceJSON}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -10,29 +10,13 @@ interface AsyncMethodProps {
|
||||
value: Record<string, string>;
|
||||
docString?: string;
|
||||
hideOutput?: boolean;
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
|
||||
const { name, parentPath, docString, value: runningTask, addNotification } = props;
|
||||
const renderCount = useRef(0);
|
||||
const formRef = useRef(null);
|
||||
const { name, parentPath, docString, value: runningTask } = props;
|
||||
|
||||
const execute = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
let method_name: string;
|
||||
const args = {};
|
||||
|
||||
if (runningTask !== undefined && runningTask !== null) {
|
||||
method_name = `stop_${name}`;
|
||||
} else {
|
||||
Object.keys(props.parameters).forEach(
|
||||
(name) => (args[name] = event.target[name].value)
|
||||
);
|
||||
method_name = `start_${name}`;
|
||||
}
|
||||
|
||||
emit_update(method_name, parentPath, { args: args });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
@ -52,6 +36,38 @@ export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
|
||||
}
|
||||
}, [runningTask]);
|
||||
|
||||
useEffect(() => {
|
||||
let message: string;
|
||||
|
||||
if (runningTask === null) {
|
||||
message = `${parentPath}.${name} task was stopped.`;
|
||||
} else {
|
||||
const runningTaskEntries = Object.entries(runningTask)
|
||||
.map(([key, value]) => `${key}: "${value}"`)
|
||||
.join(', ');
|
||||
|
||||
message = `${parentPath}.${name} was started with parameters { ${runningTaskEntries} }.`;
|
||||
}
|
||||
addNotification(message);
|
||||
}, [props.value]);
|
||||
|
||||
const execute = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
let method_name: string;
|
||||
const args = {};
|
||||
|
||||
if (runningTask !== undefined && runningTask !== null) {
|
||||
method_name = `stop_${name}`;
|
||||
} else {
|
||||
Object.keys(props.parameters).forEach(
|
||||
(name) => (args[name] = event.target[name].value)
|
||||
);
|
||||
method_name = `start_${name}`;
|
||||
}
|
||||
|
||||
emit_update(method_name, parentPath, { args: args });
|
||||
};
|
||||
|
||||
const args = Object.entries(props.parameters).map(([name, type], index) => {
|
||||
const form_name = `${name} (${type})`;
|
||||
const value = runningTask && runningTask[name];
|
||||
|
@ -10,17 +10,23 @@ interface ButtonComponentProps {
|
||||
readOnly: boolean;
|
||||
docString: string;
|
||||
mapping?: [string, string]; // Enforce a tuple of two strings
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
|
||||
const { name, parentPath, value, readOnly, docString, mapping, addNotification } =
|
||||
props;
|
||||
const buttonName = mapping ? (value ? mapping[0] : mapping[1]) : name;
|
||||
|
||||
const renderCount = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
});
|
||||
const { name, parentPath, value, readOnly, docString, mapping } = props;
|
||||
|
||||
const buttonName = mapping ? (value ? mapping[0] : mapping[1]) : name;
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name} changed to ${value}.`);
|
||||
}, [props.value]);
|
||||
|
||||
const setChecked = (checked: boolean) => {
|
||||
emit_update(name, parentPath, checked);
|
||||
|
@ -8,12 +8,18 @@ type DataServiceProps = {
|
||||
props: DataServiceJSON;
|
||||
parentPath?: string;
|
||||
isInstantUpdate: boolean;
|
||||
addNotification: (string) => void;
|
||||
};
|
||||
|
||||
export type DataServiceJSON = Record<string, Attribute>;
|
||||
|
||||
export const DataServiceComponent = React.memo(
|
||||
({ props, parentPath = 'DataService', isInstantUpdate }: DataServiceProps) => {
|
||||
({
|
||||
props,
|
||||
parentPath = 'DataService',
|
||||
isInstantUpdate,
|
||||
addNotification
|
||||
}: DataServiceProps) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
return (
|
||||
@ -35,6 +41,7 @@ export const DataServiceComponent = React.memo(
|
||||
name={key}
|
||||
parentPath={parentPath}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -9,16 +9,28 @@ interface EnumComponentProps {
|
||||
value: string;
|
||||
docString?: string;
|
||||
enumDict: Record<string, string>;
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
export const EnumComponent = React.memo((props: EnumComponentProps) => {
|
||||
const {
|
||||
name,
|
||||
parentPath: parentPath,
|
||||
value,
|
||||
docString,
|
||||
enumDict,
|
||||
addNotification
|
||||
} = props;
|
||||
|
||||
const renderCount = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
});
|
||||
|
||||
const { name, parentPath: parentPath, value, docString, enumDict } = props;
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name} changed to ${value}.`);
|
||||
}, [props.value]);
|
||||
|
||||
const handleValueChange = (newValue: string) => {
|
||||
emit_update(name, parentPath, newValue);
|
||||
|
@ -38,10 +38,17 @@ type GenericComponentProps = {
|
||||
name: string;
|
||||
parentPath: string;
|
||||
isInstantUpdate: boolean;
|
||||
addNotification: (string) => void;
|
||||
};
|
||||
|
||||
export const GenericComponent = React.memo(
|
||||
({ attribute, name, parentPath, isInstantUpdate }: GenericComponentProps) => {
|
||||
({
|
||||
attribute,
|
||||
name,
|
||||
parentPath,
|
||||
isInstantUpdate,
|
||||
addNotification
|
||||
}: GenericComponentProps) => {
|
||||
if (attribute.type === 'bool') {
|
||||
return (
|
||||
<ButtonComponent
|
||||
@ -50,6 +57,7 @@ export const GenericComponent = React.memo(
|
||||
docString={attribute.doc}
|
||||
readOnly={attribute.readonly}
|
||||
value={Boolean(attribute.value)}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else if (attribute.type === 'float' || attribute.type === 'int') {
|
||||
@ -62,6 +70,7 @@ export const GenericComponent = React.memo(
|
||||
readOnly={attribute.readonly}
|
||||
value={Number(attribute.value)}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else if (attribute.type === 'Quantity') {
|
||||
@ -75,6 +84,7 @@ export const GenericComponent = React.memo(
|
||||
value={Number(attribute.value['magnitude'])}
|
||||
unit={attribute.value['unit']}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else if (attribute.type === 'NumberSlider') {
|
||||
@ -89,6 +99,7 @@ export const GenericComponent = React.memo(
|
||||
max={attribute.value['max']['value']}
|
||||
stepSize={attribute.value['step_size']['value']}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else if (attribute.type === 'Enum') {
|
||||
@ -99,6 +110,7 @@ export const GenericComponent = React.memo(
|
||||
docString={attribute.doc}
|
||||
value={String(attribute.value)}
|
||||
enumDict={attribute.enum}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else if (attribute.type === 'method') {
|
||||
@ -109,6 +121,7 @@ export const GenericComponent = React.memo(
|
||||
parentPath={parentPath}
|
||||
docString={attribute.doc}
|
||||
parameters={attribute.parameters}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@ -119,6 +132,7 @@ export const GenericComponent = React.memo(
|
||||
docString={attribute.doc}
|
||||
parameters={attribute.parameters}
|
||||
value={attribute.value as Record<string, string>}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -131,6 +145,7 @@ export const GenericComponent = React.memo(
|
||||
docString={attribute.doc}
|
||||
parentPath={parentPath}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else if (attribute.type === 'DataService') {
|
||||
@ -139,6 +154,7 @@ export const GenericComponent = React.memo(
|
||||
props={attribute.value as DataServiceJSON}
|
||||
parentPath={parentPath.concat('.', name)}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else if (attribute.type === 'list') {
|
||||
@ -149,6 +165,7 @@ export const GenericComponent = React.memo(
|
||||
docString={attribute.doc}
|
||||
parentPath={parentPath}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else if (attribute.type === 'Image') {
|
||||
@ -161,6 +178,7 @@ export const GenericComponent = React.memo(
|
||||
docString={attribute.doc}
|
||||
// Add any other specific props for the ImageComponent here
|
||||
format={attribute.value['format']['value'] as string}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { emit_update } from '../socket';
|
||||
import { Card, Image } from 'react-bootstrap';
|
||||
import { DocStringComponent } from './DocStringComponent';
|
||||
|
||||
@ -10,16 +9,22 @@ interface ImageComponentProps {
|
||||
readOnly: boolean;
|
||||
docString: string;
|
||||
format: string;
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
export const ImageComponent = React.memo((props: ImageComponentProps) => {
|
||||
const { name, parentPath, value, docString, format, addNotification } = props;
|
||||
|
||||
const renderCount = useRef(0);
|
||||
const { name, parentPath, value, docString, format } = props;
|
||||
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name} changed.`);
|
||||
}, [props.value]);
|
||||
|
||||
return (
|
||||
<div className={'imageComponent'} id={parentPath.concat('.' + name)}>
|
||||
<Card>
|
||||
|
@ -8,16 +8,18 @@ interface ListComponentProps {
|
||||
value: Attribute[];
|
||||
docString: string;
|
||||
isInstantUpdate: boolean;
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
export const ListComponent = React.memo((props: ListComponentProps) => {
|
||||
const { name, parentPath, value, docString, isInstantUpdate, addNotification } =
|
||||
props;
|
||||
|
||||
const renderCount = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
});
|
||||
|
||||
const { name, parentPath, value, docString, isInstantUpdate } = props;
|
||||
}, [props]);
|
||||
|
||||
return (
|
||||
<div className={'listComponent'} id={parentPath.concat(name)}>
|
||||
@ -33,6 +35,7 @@ export const ListComponent = React.memo((props: ListComponentProps) => {
|
||||
name={`${name}[${index}]`}
|
||||
parentPath={parentPath}
|
||||
isInstantUpdate={isInstantUpdate}
|
||||
addNotification={addNotification}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -9,16 +9,37 @@ interface MethodProps {
|
||||
parameters: Record<string, string>;
|
||||
docString?: string;
|
||||
hideOutput?: boolean;
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
export const MethodComponent = React.memo((props: MethodProps) => {
|
||||
const { name, parentPath, docString, addNotification } = props;
|
||||
|
||||
const renderCount = useRef(0);
|
||||
const [hideOutput, setHideOutput] = useState(false);
|
||||
|
||||
// Add a new state variable to hold the list of function calls
|
||||
const [functionCalls, setFunctionCalls] = useState([]);
|
||||
|
||||
const { name, parentPath, docString } = props;
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
if (props.hideOutput !== undefined) {
|
||||
setHideOutput(props.hideOutput);
|
||||
}
|
||||
});
|
||||
|
||||
const triggerNotification = (args: Record<string, string>) => {
|
||||
const argsString = Object.entries(args)
|
||||
.map(([key, value]) => `${key}: "${value}"`)
|
||||
.join(', ');
|
||||
let message = `Method ${parentPath}.${name} was triggered`;
|
||||
|
||||
if (argsString === '') {
|
||||
message += '.';
|
||||
} else {
|
||||
message += ` with arguments {${argsString}}.`;
|
||||
}
|
||||
addNotification(message);
|
||||
};
|
||||
|
||||
const execute = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
@ -33,14 +54,9 @@ export const MethodComponent = React.memo((props: MethodProps) => {
|
||||
setFunctionCalls((prevCalls) => [...prevCalls, { name, args, result: ack }]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
if (props.hideOutput !== undefined) {
|
||||
setHideOutput(props.hideOutput);
|
||||
}
|
||||
});
|
||||
triggerNotification(args);
|
||||
};
|
||||
|
||||
const args = Object.entries(props.parameters).map(([name, type], index) => {
|
||||
const form_name = `${name} (${type})`;
|
||||
|
@ -22,6 +22,7 @@ interface NumberComponentProps {
|
||||
value: number,
|
||||
callback?: (ack: unknown) => void
|
||||
) => void;
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
// TODO: highlight the digit that is being changed by setting both selectionStart and
|
||||
@ -108,7 +109,15 @@ const handleDeleteKey = (
|
||||
};
|
||||
|
||||
export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
||||
const { name, parentPath, readOnly, docString, isInstantUpdate, unit } = props;
|
||||
const {
|
||||
name,
|
||||
parentPath,
|
||||
readOnly,
|
||||
docString,
|
||||
isInstantUpdate,
|
||||
unit,
|
||||
addNotification
|
||||
} = props;
|
||||
|
||||
// Whether to show the name infront of the component (false if used with a slider)
|
||||
const showName = props.showName !== undefined ? props.showName : true;
|
||||
@ -143,6 +152,15 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
||||
if (props.value !== numericInputString) {
|
||||
setInputString(props.value.toString());
|
||||
}
|
||||
|
||||
// emitting notification
|
||||
let notificationMsg = `${parentPath}.${name} changed to ${props.value}`;
|
||||
if (unit === undefined) {
|
||||
notificationMsg += '.';
|
||||
} else {
|
||||
notificationMsg += ` ${unit}.`;
|
||||
}
|
||||
addNotification(notificationMsg);
|
||||
}, [props.value]);
|
||||
|
||||
const handleNumericKey = (key: string, value: string, selectionStart: number) => {
|
||||
|
@ -15,6 +15,7 @@ interface SliderComponentProps {
|
||||
docString: string;
|
||||
stepSize: number;
|
||||
isInstantUpdate: boolean;
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
||||
@ -34,9 +35,26 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
||||
stepSize,
|
||||
readOnly,
|
||||
docString,
|
||||
isInstantUpdate
|
||||
isInstantUpdate,
|
||||
addNotification
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name} changed to ${value}.`);
|
||||
}, [props.value]);
|
||||
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name}.min changed to ${min}.`);
|
||||
}, [props.min]);
|
||||
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name}.max changed to ${max}.`);
|
||||
}, [props.max]);
|
||||
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name}.stepSize changed to ${stepSize}.`);
|
||||
}, [props.stepSize]);
|
||||
|
||||
const emitSliderUpdate = (
|
||||
name: string,
|
||||
parentPath: string,
|
||||
@ -122,6 +140,7 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
||||
value={value}
|
||||
showName={false}
|
||||
customEmitUpdate={emitSliderUpdate}
|
||||
addNotification={() => null}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs="auto">
|
||||
|
@ -13,13 +13,16 @@ interface StringComponentProps {
|
||||
readOnly: boolean;
|
||||
docString: string;
|
||||
isInstantUpdate: boolean;
|
||||
addNotification: (string) => void;
|
||||
}
|
||||
|
||||
export const StringComponent = React.memo((props: StringComponentProps) => {
|
||||
const { name, parentPath, readOnly, docString, isInstantUpdate, addNotification } =
|
||||
props;
|
||||
|
||||
const renderCount = useRef(0);
|
||||
const [inputString, setInputString] = useState(props.value);
|
||||
|
||||
const { name, parentPath, readOnly, docString, isInstantUpdate } = props;
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
}, [isInstantUpdate, inputString, renderCount]);
|
||||
@ -29,6 +32,7 @@ export const StringComponent = React.memo((props: StringComponentProps) => {
|
||||
if (props.value !== inputString) {
|
||||
setInputString(props.value);
|
||||
}
|
||||
addNotification(`${parentPath}.${name} changed to ${props.value}.`);
|
||||
}, [props.value]);
|
||||
|
||||
const handleChange = (event) => {
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { Notification } from '../components/NotificationsComponent';
|
||||
|
||||
export const useNotification = () => {
|
||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||
|
||||
const notify = (text: string) => {
|
||||
// Getting the current time in the required format
|
||||
const timeString = 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) => [
|
||||
{ id, text, time: timeString },
|
||||
...prevNotifications
|
||||
]);
|
||||
};
|
||||
|
||||
const removeNotificationById = (id: number) => {
|
||||
setNotifications((prevNotifications) =>
|
||||
prevNotifications.filter((n) => n.id !== id)
|
||||
);
|
||||
};
|
||||
|
||||
return { notifications, notify, removeNotificationById };
|
||||
};
|
@ -1,46 +0,0 @@
|
||||
type Data = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const STANDARD_TYPES = [
|
||||
'int',
|
||||
'float',
|
||||
'bool',
|
||||
'str',
|
||||
'Enum',
|
||||
'method',
|
||||
'NoneType',
|
||||
'Quantity'
|
||||
];
|
||||
|
||||
export function getDataServiceJSONValueByPathAndKey(
|
||||
data: Data,
|
||||
path: string,
|
||||
key = 'value'
|
||||
): string {
|
||||
// Split the path into parts
|
||||
const parts = path.split(/\.|(?=\[\d+\])/);
|
||||
parts.shift(); // Remove the first element
|
||||
|
||||
// Traverse the dictionary according to the path parts
|
||||
for (const part of parts) {
|
||||
if (part.startsWith('[')) {
|
||||
// List index
|
||||
const idx = parseInt(part.substring(1, part.length - 1)); // Strip the brackets and convert to integer
|
||||
data = data[idx];
|
||||
} else {
|
||||
// Dictionary key
|
||||
data = data[part];
|
||||
}
|
||||
|
||||
// When the attribute is a class instance, the attributes are nested in the
|
||||
// "value" key
|
||||
if (!STANDARD_TYPES.includes(data['type'])) {
|
||||
data = data['value'];
|
||||
}
|
||||
}
|
||||
|
||||
// Return the value at the terminal point of the path
|
||||
return data[key];
|
||||
}
|
Reference in New Issue
Block a user