mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-06-05 21:20:40 +02:00
moves displayName and id to GenericComponent and pass them as props
This commit is contained in:
parent
2f2544b978
commit
2d98ba51f4
@ -1,10 +1,8 @@
|
|||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { runMethod } from '../socket';
|
import { runMethod } from '../socket';
|
||||||
import { InputGroup, Form, Button } from 'react-bootstrap';
|
import { InputGroup, Form, Button } from 'react-bootstrap';
|
||||||
import { DocStringComponent } from './DocStringComponent';
|
import { DocStringComponent } from './DocStringComponent';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
|
|
||||||
type AsyncMethodProps = {
|
type AsyncMethodProps = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -14,20 +12,23 @@ type AsyncMethodProps = {
|
|||||||
docString?: string;
|
docString?: string;
|
||||||
hideOutput?: boolean;
|
hideOutput?: boolean;
|
||||||
addNotification: (message: string, levelname?: LevelName) => void;
|
addNotification: (message: string, levelname?: LevelName) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
|
export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
|
||||||
const { name, parentPath, docString, value: runningTask, addNotification } = props;
|
const {
|
||||||
|
name,
|
||||||
|
parentPath,
|
||||||
|
docString,
|
||||||
|
value: runningTask,
|
||||||
|
addNotification,
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
|
} = props;
|
||||||
const renderCount = useRef(0);
|
const renderCount = useRef(0);
|
||||||
const formRef = useRef(null);
|
const formRef = useRef(null);
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
|
||||||
const webSettings = useContext(WebSettingsContext);
|
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderCount.current++;
|
renderCount.current++;
|
||||||
@ -51,13 +52,13 @@ export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
|
|||||||
let message: string;
|
let message: string;
|
||||||
|
|
||||||
if (runningTask === null) {
|
if (runningTask === null) {
|
||||||
message = `${parentPath}.${name} task was stopped.`;
|
message = `${fullAccessPath} task was stopped.`;
|
||||||
} else {
|
} else {
|
||||||
const runningTaskEntries = Object.entries(runningTask)
|
const runningTaskEntries = Object.entries(runningTask)
|
||||||
.map(([key, value]) => `${key}: "${value}"`)
|
.map(([key, value]) => `${key}: "${value}"`)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
message = `${parentPath}.${name} was started with parameters { ${runningTaskEntries} }.`;
|
message = `${fullAccessPath} was started with parameters { ${runningTaskEntries} }.`;
|
||||||
}
|
}
|
||||||
addNotification(message);
|
addNotification(message);
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
import { ToggleButton } from 'react-bootstrap';
|
import { ToggleButton } from 'react-bootstrap';
|
||||||
import { DocStringComponent } from './DocStringComponent';
|
import { DocStringComponent } from './DocStringComponent';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
|
|
||||||
type ButtonComponentProps = {
|
type ButtonComponentProps = {
|
||||||
@ -19,27 +17,24 @@ type ButtonComponentProps = {
|
|||||||
prefix?: string,
|
prefix?: string,
|
||||||
callback?: (ack: unknown) => void
|
callback?: (ack: unknown) => void
|
||||||
) => void;
|
) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
|
export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
|
||||||
const {
|
const {
|
||||||
name,
|
|
||||||
parentPath,
|
|
||||||
value,
|
value,
|
||||||
readOnly,
|
readOnly,
|
||||||
docString,
|
docString,
|
||||||
addNotification,
|
addNotification,
|
||||||
changeCallback = () => {}
|
changeCallback = () => {},
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
} = props;
|
} = props;
|
||||||
// const buttonName = props.mapping ? (value ? props.mapping[0] : props.mapping[1]) : name;
|
// const buttonName = props.mapping ? (value ? props.mapping[0] : props.mapping[1]) : name;
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
const fullAccessPath = [props.parentPath, props.name]
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
.filter((element) => element)
|
||||||
const webSettings = useContext(WebSettingsContext);
|
.join('.');
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderCount = useRef(0);
|
const renderCount = useRef(0);
|
||||||
|
|
||||||
@ -48,7 +43,7 @@ export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNotification(`${parentPath}.${name} changed to ${value}.`);
|
addNotification(`${fullAccessPath} changed to ${value}.`);
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
|
||||||
const setChecked = (checked: boolean) => {
|
const setChecked = (checked: boolean) => {
|
||||||
@ -66,7 +61,7 @@ export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
variant={value ? 'success' : 'secondary'}
|
variant={value ? 'success' : 'secondary'}
|
||||||
checked={value}
|
checked={value}
|
||||||
value={parentPath}
|
value={displayName}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
onChange={(e) => setChecked(e.currentTarget.checked)}>
|
onChange={(e) => setChecked(e.currentTarget.checked)}>
|
||||||
{displayName}
|
{displayName}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
import { InputGroup, Form, Row, Col } from 'react-bootstrap';
|
import { InputGroup, Form, Row, Col } from 'react-bootstrap';
|
||||||
import { DocStringComponent } from './DocStringComponent';
|
import { DocStringComponent } from './DocStringComponent';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
|
|
||||||
type ColouredEnumComponentProps = {
|
type ColouredEnumComponentProps = {
|
||||||
@ -19,35 +17,32 @@ type ColouredEnumComponentProps = {
|
|||||||
prefix?: string,
|
prefix?: string,
|
||||||
callback?: (ack: unknown) => void
|
callback?: (ack: unknown) => void
|
||||||
) => void;
|
) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentProps) => {
|
export const ColouredEnumComponent = React.memo((props: ColouredEnumComponentProps) => {
|
||||||
const {
|
const {
|
||||||
name,
|
|
||||||
parentPath: parentPath,
|
|
||||||
value,
|
value,
|
||||||
docString,
|
docString,
|
||||||
enumDict,
|
enumDict,
|
||||||
readOnly,
|
readOnly,
|
||||||
addNotification,
|
addNotification,
|
||||||
changeCallback = () => {}
|
changeCallback = () => {},
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
} = props;
|
} = props;
|
||||||
const renderCount = useRef(0);
|
const renderCount = useRef(0);
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
const fullAccessPath = [props.parentPath, props.name]
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
.filter((element) => element)
|
||||||
const webSettings = useContext(WebSettingsContext);
|
.join('.');
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderCount.current++;
|
renderCount.current++;
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNotification(`${parentPath}.${name} changed to ${value}.`);
|
addNotification(`${fullAccessPath} changed to ${value}.`);
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, Collapse } from 'react-bootstrap';
|
import { Card, Collapse } from 'react-bootstrap';
|
||||||
import { ChevronDown, ChevronRight } from 'react-bootstrap-icons';
|
import { ChevronDown, ChevronRight } from 'react-bootstrap-icons';
|
||||||
import { Attribute, GenericComponent } from './GenericComponent';
|
import { SerializedValue, GenericComponent } from './GenericComponent';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
|
|
||||||
type DataServiceProps = {
|
type DataServiceProps = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -13,31 +11,24 @@ type DataServiceProps = {
|
|||||||
parentPath?: string;
|
parentPath?: string;
|
||||||
isInstantUpdate: boolean;
|
isInstantUpdate: boolean;
|
||||||
addNotification: (message: string, levelname?: LevelName) => void;
|
addNotification: (message: string, levelname?: LevelName) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DataServiceJSON = Record<string, Attribute>;
|
export type DataServiceJSON = Record<string, SerializedValue>;
|
||||||
|
|
||||||
export const DataServiceComponent = React.memo(
|
export const DataServiceComponent = React.memo(
|
||||||
({
|
({
|
||||||
name,
|
name,
|
||||||
props,
|
props,
|
||||||
parentPath = '',
|
parentPath = undefined,
|
||||||
isInstantUpdate,
|
isInstantUpdate,
|
||||||
addNotification
|
addNotification,
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
}: DataServiceProps) => {
|
}: DataServiceProps) => {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
let fullAccessPath = parentPath;
|
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
||||||
if (name) {
|
|
||||||
fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
|
||||||
}
|
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
|
||||||
|
|
||||||
const webSettings = useContext(WebSettingsContext);
|
|
||||||
let displayName = fullAccessPath;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayName !== '') {
|
if (displayName !== '') {
|
||||||
return (
|
return (
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import { useContext } from 'react';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
import { DataServiceComponent, DataServiceJSON } from './DataServiceComponent';
|
import { DataServiceComponent, DataServiceJSON } from './DataServiceComponent';
|
||||||
import { MethodComponent } from './MethodComponent';
|
import { MethodComponent } from './MethodComponent';
|
||||||
|
|
||||||
@ -12,6 +9,8 @@ type DeviceConnectionProps = {
|
|||||||
parentPath: string;
|
parentPath: string;
|
||||||
isInstantUpdate: boolean;
|
isInstantUpdate: boolean;
|
||||||
addNotification: (message: string, levelname?: LevelName) => void;
|
addNotification: (message: string, levelname?: LevelName) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeviceConnectionComponent = React.memo(
|
export const DeviceConnectionComponent = React.memo(
|
||||||
@ -20,23 +19,14 @@ export const DeviceConnectionComponent = React.memo(
|
|||||||
props,
|
props,
|
||||||
parentPath,
|
parentPath,
|
||||||
isInstantUpdate,
|
isInstantUpdate,
|
||||||
addNotification
|
addNotification,
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
}: DeviceConnectionProps) => {
|
}: DeviceConnectionProps) => {
|
||||||
const { connected, connect, ...updatedProps } = props;
|
const { connected, connect, ...updatedProps } = props;
|
||||||
const connectedVal = connected.value;
|
const connectedVal = connected.value;
|
||||||
|
|
||||||
let fullAccessPath = parentPath;
|
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
||||||
if (name) {
|
|
||||||
fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
|
||||||
}
|
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
|
||||||
|
|
||||||
const webSettings = useContext(WebSettingsContext);
|
|
||||||
let displayName = fullAccessPath;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="deviceConnectionComponent" id={id}>
|
<div className="deviceConnectionComponent" id={id}>
|
||||||
@ -51,6 +41,8 @@ export const DeviceConnectionComponent = React.memo(
|
|||||||
parameters={connect.parameters}
|
parameters={connect.parameters}
|
||||||
docString={connect.doc}
|
docString={connect.doc}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
|
displayName={'reconnect'}
|
||||||
|
id={id + '-connect'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -60,6 +52,8 @@ export const DeviceConnectionComponent = React.memo(
|
|||||||
parentPath={parentPath}
|
parentPath={parentPath}
|
||||||
isInstantUpdate={isInstantUpdate}
|
isInstantUpdate={isInstantUpdate}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
import { InputGroup, Form, Row, Col } from 'react-bootstrap';
|
import { InputGroup, Form, Row, Col } from 'react-bootstrap';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { DocStringComponent } from './DocStringComponent';
|
import { DocStringComponent } from './DocStringComponent';
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
|
|
||||||
@ -18,35 +16,32 @@ type EnumComponentProps = {
|
|||||||
prefix?: string,
|
prefix?: string,
|
||||||
callback?: (ack: unknown) => void
|
callback?: (ack: unknown) => void
|
||||||
) => void;
|
) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EnumComponent = React.memo((props: EnumComponentProps) => {
|
export const EnumComponent = React.memo((props: EnumComponentProps) => {
|
||||||
const {
|
const {
|
||||||
name,
|
|
||||||
parentPath: parentPath,
|
|
||||||
value,
|
value,
|
||||||
docString,
|
docString,
|
||||||
enumDict,
|
enumDict,
|
||||||
addNotification,
|
addNotification,
|
||||||
changeCallback = () => {}
|
changeCallback = () => {},
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const renderCount = useRef(0);
|
const renderCount = useRef(0);
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
const fullAccessPath = [props.parentPath, props.name]
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
.filter((element) => element)
|
||||||
const webSettings = useContext(WebSettingsContext);
|
.join('.');
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderCount.current++;
|
renderCount.current++;
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNotification(`${parentPath}.${name} changed to ${value}.`);
|
addNotification(`${fullAccessPath} changed to ${value}.`);
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { ButtonComponent } from './ButtonComponent';
|
import { ButtonComponent } from './ButtonComponent';
|
||||||
import { NumberComponent } from './NumberComponent';
|
import { NumberComponent } from './NumberComponent';
|
||||||
import { SliderComponent } from './SliderComponent';
|
import { SliderComponent } from './SliderComponent';
|
||||||
@ -12,6 +12,8 @@ import { DeviceConnectionComponent } from './DeviceConnection';
|
|||||||
import { ImageComponent } from './ImageComponent';
|
import { ImageComponent } from './ImageComponent';
|
||||||
import { ColouredEnumComponent } from './ColouredEnumComponent';
|
import { ColouredEnumComponent } from './ColouredEnumComponent';
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
|
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
||||||
|
import { WebSettingsContext } from '../WebSettings';
|
||||||
import { setAttribute } from '../socket';
|
import { setAttribute } from '../socket';
|
||||||
|
|
||||||
type AttributeType =
|
type AttributeType =
|
||||||
@ -35,7 +37,7 @@ export type SerializedValue = {
|
|||||||
value?: ValueType | ValueType[];
|
value?: ValueType | ValueType[];
|
||||||
readonly: boolean;
|
readonly: boolean;
|
||||||
doc?: string | null;
|
doc?: string | null;
|
||||||
parameters?: Record<string, string>;
|
parameters?: Record<string, SerializedValue>;
|
||||||
async?: boolean;
|
async?: boolean;
|
||||||
enum?: Record<string, string>;
|
enum?: Record<string, string>;
|
||||||
};
|
};
|
||||||
@ -55,6 +57,14 @@ export const GenericComponent = React.memo(
|
|||||||
isInstantUpdate,
|
isInstantUpdate,
|
||||||
addNotification
|
addNotification
|
||||||
}: GenericComponentProps) => {
|
}: GenericComponentProps) => {
|
||||||
|
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
||||||
|
const id = getIdFromFullAccessPath(fullAccessPath);
|
||||||
|
const webSettings = useContext(WebSettingsContext);
|
||||||
|
let displayName = name;
|
||||||
|
|
||||||
|
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
||||||
|
displayName = webSettings[fullAccessPath].displayName;
|
||||||
|
}
|
||||||
|
|
||||||
function changeCallback(
|
function changeCallback(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
@ -75,6 +85,8 @@ export const GenericComponent = React.memo(
|
|||||||
value={Boolean(attribute.value)}
|
value={Boolean(attribute.value)}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
changeCallback={changeCallback}
|
changeCallback={changeCallback}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'float' || attribute.type === 'int') {
|
} else if (attribute.type === 'float' || attribute.type === 'int') {
|
||||||
@ -89,6 +101,8 @@ export const GenericComponent = React.memo(
|
|||||||
isInstantUpdate={isInstantUpdate}
|
isInstantUpdate={isInstantUpdate}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
changeCallback={changeCallback}
|
changeCallback={changeCallback}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'Quantity') {
|
} else if (attribute.type === 'Quantity') {
|
||||||
@ -104,6 +118,8 @@ export const GenericComponent = React.memo(
|
|||||||
isInstantUpdate={isInstantUpdate}
|
isInstantUpdate={isInstantUpdate}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
changeCallback={changeCallback}
|
changeCallback={changeCallback}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'NumberSlider') {
|
} else if (attribute.type === 'NumberSlider') {
|
||||||
@ -120,6 +136,8 @@ export const GenericComponent = React.memo(
|
|||||||
isInstantUpdate={isInstantUpdate}
|
isInstantUpdate={isInstantUpdate}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
changeCallback={changeCallback}
|
changeCallback={changeCallback}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'Enum') {
|
} else if (attribute.type === 'Enum') {
|
||||||
@ -132,6 +150,8 @@ export const GenericComponent = React.memo(
|
|||||||
enumDict={attribute.enum}
|
enumDict={attribute.enum}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
changeCallback={changeCallback}
|
changeCallback={changeCallback}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'method') {
|
} else if (attribute.type === 'method') {
|
||||||
@ -143,6 +163,8 @@ export const GenericComponent = React.memo(
|
|||||||
docString={attribute.doc}
|
docString={attribute.doc}
|
||||||
parameters={attribute.parameters}
|
parameters={attribute.parameters}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -154,6 +176,8 @@ export const GenericComponent = React.memo(
|
|||||||
parameters={attribute.parameters}
|
parameters={attribute.parameters}
|
||||||
value={attribute.value as Record<string, string>}
|
value={attribute.value as Record<string, string>}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -168,6 +192,8 @@ export const GenericComponent = React.memo(
|
|||||||
isInstantUpdate={isInstantUpdate}
|
isInstantUpdate={isInstantUpdate}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
changeCallback={changeCallback}
|
changeCallback={changeCallback}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'DataService') {
|
} else if (attribute.type === 'DataService') {
|
||||||
@ -178,6 +204,8 @@ export const GenericComponent = React.memo(
|
|||||||
parentPath={parentPath}
|
parentPath={parentPath}
|
||||||
isInstantUpdate={isInstantUpdate}
|
isInstantUpdate={isInstantUpdate}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'DeviceConnection') {
|
} else if (attribute.type === 'DeviceConnection') {
|
||||||
@ -188,6 +216,8 @@ export const GenericComponent = React.memo(
|
|||||||
parentPath={parentPath}
|
parentPath={parentPath}
|
||||||
isInstantUpdate={isInstantUpdate}
|
isInstantUpdate={isInstantUpdate}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'list') {
|
} else if (attribute.type === 'list') {
|
||||||
@ -199,6 +229,7 @@ export const GenericComponent = React.memo(
|
|||||||
parentPath={parentPath}
|
parentPath={parentPath}
|
||||||
isInstantUpdate={isInstantUpdate}
|
isInstantUpdate={isInstantUpdate}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'Image') {
|
} else if (attribute.type === 'Image') {
|
||||||
@ -212,6 +243,8 @@ export const GenericComponent = React.memo(
|
|||||||
// Add any other specific props for the ImageComponent here
|
// Add any other specific props for the ImageComponent here
|
||||||
format={attribute.value['format']['value'] as string}
|
format={attribute.value['format']['value'] as string}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attribute.type === 'ColouredEnum') {
|
} else if (attribute.type === 'ColouredEnum') {
|
||||||
@ -225,6 +258,8 @@ export const GenericComponent = React.memo(
|
|||||||
enumDict={attribute.enum}
|
enumDict={attribute.enum}
|
||||||
addNotification={addNotification}
|
addNotification={addNotification}
|
||||||
changeCallback={changeCallback}
|
changeCallback={changeCallback}
|
||||||
|
displayName={displayName}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
import { Card, Collapse, Image } from 'react-bootstrap';
|
import { Card, Collapse, Image } from 'react-bootstrap';
|
||||||
import { DocStringComponent } from './DocStringComponent';
|
import { DocStringComponent } from './DocStringComponent';
|
||||||
import { ChevronDown, ChevronRight } from 'react-bootstrap-icons';
|
import { ChevronDown, ChevronRight } from 'react-bootstrap-icons';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
|
|
||||||
type ImageComponentProps = {
|
type ImageComponentProps = {
|
||||||
@ -14,28 +12,25 @@ type ImageComponentProps = {
|
|||||||
docString: string;
|
docString: string;
|
||||||
format: string;
|
format: string;
|
||||||
addNotification: (message: string, levelname?: LevelName) => void;
|
addNotification: (message: string, levelname?: LevelName) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImageComponent = React.memo((props: ImageComponentProps) => {
|
export const ImageComponent = React.memo((props: ImageComponentProps) => {
|
||||||
const { name, parentPath, value, docString, format, addNotification } = props;
|
const { value, docString, format, addNotification, displayName, id } = props;
|
||||||
|
|
||||||
const renderCount = useRef(0);
|
const renderCount = useRef(0);
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
const fullAccessPath = [props.parentPath, props.name]
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
.filter((element) => element)
|
||||||
const webSettings = useContext(WebSettingsContext);
|
.join('.');
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderCount.current++;
|
renderCount.current++;
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNotification(`${parentPath}.${name} changed.`);
|
addNotification(`${fullAccessPath} changed.`);
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,25 +1,23 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { DocStringComponent } from './DocStringComponent';
|
import { DocStringComponent } from './DocStringComponent';
|
||||||
import { Attribute, GenericComponent } from './GenericComponent';
|
import { SerializedValue, GenericComponent } from './GenericComponent';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
|
|
||||||
type ListComponentProps = {
|
type ListComponentProps = {
|
||||||
name: string;
|
name: string;
|
||||||
parentPath?: string;
|
parentPath?: string;
|
||||||
value: Attribute[];
|
value: SerializedValue[];
|
||||||
docString: string;
|
docString: string;
|
||||||
isInstantUpdate: boolean;
|
isInstantUpdate: boolean;
|
||||||
addNotification: (message: string, levelname?: LevelName) => void;
|
addNotification: (message: string, levelname?: LevelName) => void;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ListComponent = React.memo((props: ListComponentProps) => {
|
export const ListComponent = React.memo((props: ListComponentProps) => {
|
||||||
const { name, parentPath, value, docString, isInstantUpdate, addNotification } =
|
const { name, parentPath, value, docString, isInstantUpdate, addNotification, id } =
|
||||||
props;
|
props;
|
||||||
|
|
||||||
const renderCount = useRef(0);
|
const renderCount = useRef(0);
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderCount.current++;
|
renderCount.current++;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
import '../App.css';
|
import '../App.css';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
import { NumberInputField } from './NumberInputField';
|
import { NumberInputField } from './NumberInputField';
|
||||||
|
|
||||||
@ -39,7 +37,6 @@ type NumberComponentProps = {
|
|||||||
docString: string;
|
docString: string;
|
||||||
isInstantUpdate: boolean;
|
isInstantUpdate: boolean;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
showName?: boolean;
|
|
||||||
addNotification: (message: string, levelname?: LevelName) => void;
|
addNotification: (message: string, levelname?: LevelName) => void;
|
||||||
changeCallback?: (
|
changeCallback?: (
|
||||||
value: unknown,
|
value: unknown,
|
||||||
@ -47,37 +44,31 @@ type NumberComponentProps = {
|
|||||||
prefix?: string,
|
prefix?: string,
|
||||||
callback?: (ack: unknown) => void
|
callback?: (ack: unknown) => void
|
||||||
) => void;
|
) => void;
|
||||||
|
displayName?: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
||||||
const {
|
const {
|
||||||
name,
|
|
||||||
value,
|
value,
|
||||||
parentPath,
|
|
||||||
readOnly,
|
readOnly,
|
||||||
docString,
|
docString,
|
||||||
isInstantUpdate,
|
isInstantUpdate,
|
||||||
unit,
|
unit,
|
||||||
addNotification,
|
addNotification,
|
||||||
changeCallback = () => {}
|
changeCallback = () => {},
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// Whether to show the name infront of the component (false if used with a slider)
|
|
||||||
const showName = props.showName !== undefined ? props.showName : true;
|
|
||||||
|
|
||||||
const renderCount = useRef(0);
|
const renderCount = useRef(0);
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
const fullAccessPath = [props.parentPath, props.name]
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
.filter((element) => element)
|
||||||
const webSettings = useContext(WebSettingsContext);
|
.join('.');
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// emitting notification
|
// emitting notification
|
||||||
let notificationMsg = `${parentPath}.${name} changed to ${props.value}`;
|
let notificationMsg = `${fullAccessPath} changed to ${props.value}`;
|
||||||
if (unit === undefined) {
|
if (unit === undefined) {
|
||||||
notificationMsg += '.';
|
notificationMsg += '.';
|
||||||
} else {
|
} else {
|
||||||
@ -94,7 +85,7 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
|||||||
<NumberInputField
|
<NumberInputField
|
||||||
name={fullAccessPath}
|
name={fullAccessPath}
|
||||||
value={value}
|
value={value}
|
||||||
displayName={showName === true ? displayName : null}
|
displayName={displayName}
|
||||||
unit={unit}
|
unit={unit}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
import { InputGroup, Form, Row, Col, Collapse, ToggleButton } from 'react-bootstrap';
|
import { InputGroup, Form, Row, Col, Collapse, ToggleButton } from 'react-bootstrap';
|
||||||
import { DocStringComponent } from './DocStringComponent';
|
import { DocStringComponent } from './DocStringComponent';
|
||||||
import { Slider } from '@mui/material';
|
import { Slider } from '@mui/material';
|
||||||
import { NumberComponent, NumberObject } from './NumberComponent';
|
import { NumberComponent, NumberObject } from './NumberComponent';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
|
|
||||||
type SliderComponentProps = {
|
type SliderComponentProps = {
|
||||||
@ -24,6 +22,8 @@ type SliderComponentProps = {
|
|||||||
prefix?: string,
|
prefix?: string,
|
||||||
callback?: (ack: unknown) => void
|
callback?: (ack: unknown) => void
|
||||||
) => void;
|
) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
||||||
@ -39,35 +39,30 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
|||||||
docString,
|
docString,
|
||||||
isInstantUpdate,
|
isInstantUpdate,
|
||||||
addNotification,
|
addNotification,
|
||||||
changeCallback = () => {}
|
changeCallback = () => {},
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
} = props;
|
} = props;
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
|
||||||
const webSettings = useContext(WebSettingsContext);
|
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderCount.current++;
|
renderCount.current++;
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNotification(`${parentPath}.${name} changed to ${value}.`);
|
addNotification(`${fullAccessPath} changed to ${value.value}.`);
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNotification(`${parentPath}.${name}.min changed to ${min}.`);
|
addNotification(`${fullAccessPath}.min changed to ${min.value}.`);
|
||||||
}, [props.min]);
|
}, [props.min]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNotification(`${parentPath}.${name}.max changed to ${max}.`);
|
addNotification(`${fullAccessPath}.max changed to ${max.value}.`);
|
||||||
}, [props.max]);
|
}, [props.max]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNotification(`${parentPath}.${name}.stepSize changed to ${stepSize}.`);
|
addNotification(`${fullAccessPath}.stepSize changed to ${stepSize.value}.`);
|
||||||
}, [props.stepSize]);
|
}, [props.stepSize]);
|
||||||
|
|
||||||
const handleOnChange = (event, newNumber: number | number[]) => {
|
const handleOnChange = (event, newNumber: number | number[]) => {
|
||||||
@ -145,8 +140,9 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
|||||||
type="float"
|
type="float"
|
||||||
value={valueMagnitude}
|
value={valueMagnitude}
|
||||||
unit={valueUnit}
|
unit={valueUnit}
|
||||||
showName={false}
|
addNotification={() => {}}
|
||||||
addNotification={() => null}
|
changeCallback={(value) => changeCallback(value, name + '.value')}
|
||||||
|
id={id + '-value'}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="auto">
|
<Col xs="auto">
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { Form, InputGroup } from 'react-bootstrap';
|
import { Form, InputGroup } from 'react-bootstrap';
|
||||||
import { DocStringComponent } from './DocStringComponent';
|
import { DocStringComponent } from './DocStringComponent';
|
||||||
import '../App.css';
|
import '../App.css';
|
||||||
import { getIdFromFullAccessPath } from '../utils/stringUtils';
|
|
||||||
import { LevelName } from './NotificationsComponent';
|
import { LevelName } from './NotificationsComponent';
|
||||||
import { WebSettingsContext } from '../WebSettings';
|
|
||||||
|
|
||||||
// TODO: add button functionality
|
// TODO: add button functionality
|
||||||
|
|
||||||
@ -22,29 +20,26 @@ type StringComponentProps = {
|
|||||||
prefix?: string,
|
prefix?: string,
|
||||||
callback?: (ack: unknown) => void
|
callback?: (ack: unknown) => void
|
||||||
) => void;
|
) => void;
|
||||||
|
displayName: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StringComponent = React.memo((props: StringComponentProps) => {
|
export const StringComponent = React.memo((props: StringComponentProps) => {
|
||||||
const {
|
const {
|
||||||
name,
|
|
||||||
parentPath,
|
|
||||||
readOnly,
|
readOnly,
|
||||||
docString,
|
docString,
|
||||||
isInstantUpdate,
|
isInstantUpdate,
|
||||||
addNotification,
|
addNotification,
|
||||||
changeCallback = () => {}
|
changeCallback = () => {},
|
||||||
|
displayName,
|
||||||
|
id
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const renderCount = useRef(0);
|
const renderCount = useRef(0);
|
||||||
const [inputString, setInputString] = useState(props.value);
|
const [inputString, setInputString] = useState(props.value);
|
||||||
const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
|
const fullAccessPath = [props.parentPath, props.name]
|
||||||
const id = getIdFromFullAccessPath(fullAccessPath);
|
.filter((element) => element)
|
||||||
const webSettings = useContext(WebSettingsContext);
|
.join('.');
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
if (webSettings[fullAccessPath] && webSettings[fullAccessPath].displayName) {
|
|
||||||
displayName = webSettings[fullAccessPath].displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderCount.current++;
|
renderCount.current++;
|
||||||
@ -55,7 +50,7 @@ export const StringComponent = React.memo((props: StringComponentProps) => {
|
|||||||
if (props.value !== inputString) {
|
if (props.value !== inputString) {
|
||||||
setInputString(props.value);
|
setInputString(props.value);
|
||||||
}
|
}
|
||||||
addNotification(`${parentPath}.${name} changed to ${props.value}.`);
|
addNotification(`${fullAccessPath} changed to ${props.value}.`);
|
||||||
}, [props.value]);
|
}, [props.value]);
|
||||||
|
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
@ -91,7 +86,6 @@ export const StringComponent = React.memo((props: StringComponentProps) => {
|
|||||||
type="text"
|
type="text"
|
||||||
value={inputString}
|
value={inputString}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
name={name}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user