mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-04-20 00:10:03 +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:
parent
8205e4d463
commit
f7579c3a89
@ -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];
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.398bc7f8.css",
|
||||
"main.js": "/static/js/main.799e0879.js",
|
||||
"main.js": "/static/js/main.39f6b50e.js",
|
||||
"index.html": "/index.html",
|
||||
"main.398bc7f8.css.map": "/static/css/main.398bc7f8.css.map",
|
||||
"main.799e0879.js.map": "/static/js/main.799e0879.js.map"
|
||||
"main.39f6b50e.js.map": "/static/js/main.39f6b50e.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.398bc7f8.css",
|
||||
"static/js/main.799e0879.js"
|
||||
"static/js/main.39f6b50e.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.799e0879.js"></script><link href="/static/css/main.398bc7f8.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.39f6b50e.js"></script><link href="/static/css/main.398bc7f8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
3
src/pydase/frontend/static/js/main.39f6b50e.js
Normal file
3
src/pydase/frontend/static/js/main.39f6b50e.js
Normal file
File diff suppressed because one or more lines are too long
1
src/pydase/frontend/static/js/main.39f6b50e.js.map
Normal file
1
src/pydase/frontend/static/js/main.39f6b50e.js.map
Normal file
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
Loading…
x
Reference in New Issue
Block a user