defines changeCallback function in GenericComponent and passes it to components (instead of setAttribute)

The components do not use the setAttribute method themselves anymore. This way, you can provide
the changeCallback function if you want and thus reuse the components.
This commit is contained in:
Mose Müller 2024-02-21 08:32:59 +01:00
parent 2bb02a5558
commit fffe679bf0
7 changed files with 88 additions and 32 deletions

View File

@ -1,7 +1,6 @@
import React, { useContext, useEffect, useRef } from 'react';
import { WebSettingsContext } from '../WebSettings';
import { ToggleButton } from 'react-bootstrap';
import { setAttribute } from '../socket';
import { DocStringComponent } from './DocStringComponent';
import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent';
@ -14,10 +13,24 @@ type ButtonComponentProps = {
docString: string;
mapping?: [string, string]; // Enforce a tuple of two strings
addNotification: (message: string, levelname?: LevelName) => void;
changeCallback?: (
value: unknown,
attributeName?: string,
prefix?: string,
callback?: (ack: unknown) => void
) => void;
};
export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
const { name, parentPath, value, readOnly, docString, addNotification } = props;
const {
name,
parentPath,
value,
readOnly,
docString,
addNotification,
changeCallback = () => {}
} = props;
// const buttonName = props.mapping ? (value ? props.mapping[0] : props.mapping[1]) : name;
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
const id = getIdFromFullAccessPath(fullAccessPath);
@ -39,7 +52,7 @@ export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
}, [props.value]);
const setChecked = (checked: boolean) => {
setAttribute(name, parentPath, checked);
changeCallback(checked);
};
return (

View File

@ -1,7 +1,6 @@
import React, { useContext, useEffect, useRef } from 'react';
import { WebSettingsContext } from '../WebSettings';
import { InputGroup, Form, Row, Col } from 'react-bootstrap';
import { setAttribute } from '../socket';
import { DocStringComponent } from './DocStringComponent';
import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent';
@ -14,6 +13,12 @@ type ColouredEnumComponentProps = {
readOnly: boolean;
enumDict: Record<string, string>;
addNotification: (message: string, levelname?: LevelName) => void;
changeCallback?: (
value: unknown,
attributeName?: string,
prefix?: string,
callback?: (ack: unknown) => void
) => void;
};
export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentProps) => {
@ -24,7 +29,8 @@ export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentPro
docString,
enumDict,
readOnly,
addNotification
addNotification,
changeCallback = () => {}
} = props;
const renderCount = useRef(0);
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
@ -44,10 +50,6 @@ export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentPro
addNotification(`${parentPath}.${name} changed to ${value}.`);
}, [props.value]);
const handleValueChange = (newValue: string) => {
setAttribute(name, parentPath, newValue);
};
return (
<div className={'component enumComponent'} id={id}>
{process.env.NODE_ENV === 'development' && (
@ -72,7 +74,7 @@ export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentPro
aria-label="coloured-enum-select"
value={value}
style={{ backgroundColor: enumDict[value] }}
onChange={(event) => handleValueChange(event.target.value)}>
onChange={(event) => changeCallback(event.target.value)}>
{Object.entries(enumDict).map(([key]) => (
<option key={key} value={key}>
{key}

View File

@ -1,7 +1,6 @@
import React, { useContext, useEffect, useRef } from 'react';
import { WebSettingsContext } from '../WebSettings';
import { InputGroup, Form, Row, Col } from 'react-bootstrap';
import { setAttribute } from '../socket';
import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { DocStringComponent } from './DocStringComponent';
import { LevelName } from './NotificationsComponent';
@ -13,6 +12,12 @@ type EnumComponentProps = {
docString?: string;
enumDict: Record<string, string>;
addNotification: (message: string, levelname?: LevelName) => void;
changeCallback?: (
value: unknown,
attributeName?: string,
prefix?: string,
callback?: (ack: unknown) => void
) => void;
};
export const EnumComponent = React.memo((props: EnumComponentProps) => {
@ -22,7 +27,8 @@ export const EnumComponent = React.memo((props: EnumComponentProps) => {
value,
docString,
enumDict,
addNotification
addNotification,
changeCallback = () => {}
} = props;
const renderCount = useRef(0);
@ -43,10 +49,6 @@ export const EnumComponent = React.memo((props: EnumComponentProps) => {
addNotification(`${parentPath}.${name} changed to ${value}.`);
}, [props.value]);
const handleValueChange = (newValue: string) => {
setAttribute(name, parentPath, newValue);
};
return (
<div className={'component enumComponent'} id={id}>
{process.env.NODE_ENV === 'development' && (
@ -61,7 +63,7 @@ export const EnumComponent = React.memo((props: EnumComponentProps) => {
<Form.Select
aria-label="Default select example"
value={value}
onChange={(event) => handleValueChange(event.target.value)}>
onChange={(event) => changeCallback(event.target.value)}>
{Object.entries(enumDict).map(([key, val]) => (
<option key={key} value={key}>
{key} - {val}

View File

@ -12,6 +12,7 @@ import { DeviceConnectionComponent } from './DeviceConnection';
import { ImageComponent } from './ImageComponent';
import { ColouredEnumComponent } from './ColouredEnumComponent';
import { LevelName } from './NotificationsComponent';
import { setAttribute } from '../socket';
type AttributeType =
| 'str'
@ -54,6 +55,16 @@ export const GenericComponent = React.memo(
isInstantUpdate,
addNotification
}: GenericComponentProps) => {
function changeCallback(
value: unknown,
attributeName: string = name,
prefix: string = parentPath,
callback: (ack: unknown) => void = undefined
) {
setAttribute(attributeName, prefix, value, callback);
}
if (attribute.type === 'bool') {
return (
<ButtonComponent
@ -63,6 +74,7 @@ export const GenericComponent = React.memo(
readOnly={attribute.readonly}
value={Boolean(attribute.value)}
addNotification={addNotification}
changeCallback={changeCallback}
/>
);
} else if (attribute.type === 'float' || attribute.type === 'int') {
@ -76,6 +88,7 @@ export const GenericComponent = React.memo(
value={Number(attribute.value)}
isInstantUpdate={isInstantUpdate}
addNotification={addNotification}
changeCallback={changeCallback}
/>
);
} else if (attribute.type === 'Quantity') {
@ -90,6 +103,7 @@ export const GenericComponent = React.memo(
unit={attribute.value['unit']}
isInstantUpdate={isInstantUpdate}
addNotification={addNotification}
changeCallback={changeCallback}
/>
);
} else if (attribute.type === 'NumberSlider') {
@ -105,6 +119,7 @@ export const GenericComponent = React.memo(
stepSize={attribute.value['step_size']}
isInstantUpdate={isInstantUpdate}
addNotification={addNotification}
changeCallback={changeCallback}
/>
);
} else if (attribute.type === 'Enum') {
@ -116,6 +131,7 @@ export const GenericComponent = React.memo(
value={String(attribute.value)}
enumDict={attribute.enum}
addNotification={addNotification}
changeCallback={changeCallback}
/>
);
} else if (attribute.type === 'method') {
@ -151,6 +167,7 @@ export const GenericComponent = React.memo(
parentPath={parentPath}
isInstantUpdate={isInstantUpdate}
addNotification={addNotification}
changeCallback={changeCallback}
/>
);
} else if (attribute.type === 'DataService') {
@ -207,6 +224,7 @@ export const GenericComponent = React.memo(
readOnly={attribute.readonly}
enumDict={attribute.enum}
addNotification={addNotification}
changeCallback={changeCallback}
/>
);
} else {

View File

@ -4,7 +4,6 @@ import '../App.css';
import { getIdFromFullAccessPath } from '../utils/stringUtils';
import { LevelName } from './NotificationsComponent';
import { NumberInputField } from './NumberInputField';
import { setAttribute } from '../socket';
// TODO: add button functionality
@ -42,6 +41,12 @@ type NumberComponentProps = {
unit?: string;
showName?: boolean;
addNotification: (message: string, levelname?: LevelName) => void;
changeCallback?: (
value: unknown,
attributeName?: string,
prefix?: string,
callback?: (ack: unknown) => void
) => void;
};
export const NumberComponent = React.memo((props: NumberComponentProps) => {
@ -53,12 +58,10 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
docString,
isInstantUpdate,
unit,
addNotification
addNotification,
changeCallback = () => {}
} = props;
function changeCallback(value: number) {
setAttribute(name, parentPath, value);
}
// Whether to show the name infront of the component (false if used with a slider)
const showName = props.showName !== undefined ? props.showName : true;

View File

@ -1,7 +1,6 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import { WebSettingsContext } from '../WebSettings';
import { InputGroup, Form, Row, Col, Collapse, ToggleButton } from 'react-bootstrap';
import { setAttribute } from '../socket';
import { DocStringComponent } from './DocStringComponent';
import { Slider } from '@mui/material';
import { NumberComponent, NumberObject } from './NumberComponent';
@ -19,6 +18,12 @@ type SliderComponentProps = {
stepSize: NumberObject;
isInstantUpdate: boolean;
addNotification: (message: string, levelname?: LevelName) => void;
changeCallback?: (
value: unknown,
attributeName?: string,
prefix?: string,
callback?: (ack: unknown) => void
) => void;
};
export const SliderComponent = React.memo((props: SliderComponentProps) => {
@ -33,7 +38,8 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
stepSize,
docString,
isInstantUpdate,
addNotification
addNotification,
changeCallback = () => {}
} = props;
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
const id = getIdFromFullAccessPath(fullAccessPath);
@ -70,11 +76,11 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
if (Array.isArray(newNumber)) {
newNumber = newNumber[0];
}
setAttribute(`${name}.value`, parentPath, newNumber);
changeCallback(newNumber, `${name}.value`);
};
const handleValueChange = (newValue: number, valueType: string) => {
setAttribute(`${name}.${valueType}`, parentPath, newValue);
changeCallback(newValue, `${name}.${valueType}`);
};
const deconstructNumberDict = (

View File

@ -1,6 +1,5 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import { Form, InputGroup } from 'react-bootstrap';
import { setAttribute } from '../socket';
import { DocStringComponent } from './DocStringComponent';
import '../App.css';
import { getIdFromFullAccessPath } from '../utils/stringUtils';
@ -17,11 +16,24 @@ type StringComponentProps = {
docString: string;
isInstantUpdate: boolean;
addNotification: (message: string, levelname?: LevelName) => void;
changeCallback?: (
value: unknown,
attributeName?: string,
prefix?: string,
callback?: (ack: unknown) => void
) => void;
};
export const StringComponent = React.memo((props: StringComponentProps) => {
const { name, parentPath, readOnly, docString, isInstantUpdate, addNotification } =
props;
const {
name,
parentPath,
readOnly,
docString,
isInstantUpdate,
addNotification,
changeCallback = () => {}
} = props;
const renderCount = useRef(0);
const [inputString, setInputString] = useState(props.value);
@ -49,19 +61,19 @@ export const StringComponent = React.memo((props: StringComponentProps) => {
const handleChange = (event) => {
setInputString(event.target.value);
if (isInstantUpdate) {
setAttribute(name, parentPath, event.target.value);
changeCallback(event.target.value);
}
};
const handleKeyDown = (event) => {
if (event.key === 'Enter' && !isInstantUpdate) {
setAttribute(name, parentPath, inputString);
changeCallback(inputString);
}
};
const handleBlur = () => {
if (!isInstantUpdate) {
setAttribute(name, parentPath, inputString);
changeCallback(inputString);
}
};