only methods without arguments can be rendered

This commit is contained in:
Mose Müller 2024-02-27 12:58:08 +01:00
parent b6f6b3058e
commit 2337aa9d6d
2 changed files with 33 additions and 169 deletions

View File

@ -1,19 +1,19 @@
import React, { 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 { Form, Button, InputGroup } from 'react-bootstrap';
import { DocStringComponent } from './DocStringComponent'; import { DocStringComponent } from './DocStringComponent';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
type AsyncMethodProps = { type AsyncMethodProps = {
name: string; name: string;
parentPath: string; parentPath: string;
parameters: Record<string, string>;
value: Record<string, string>; value: Record<string, string>;
docString?: string; docString?: string;
hideOutput?: boolean; hideOutput?: boolean;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
displayName: string; displayName: string;
id: string; id: string;
render: boolean;
}; };
export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => { export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
@ -26,6 +26,12 @@ export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
displayName, displayName,
id id
} = props; } = props;
// Conditional rendering based on the 'render' prop.
if (!props.render) {
return null;
}
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('.');
@ -66,50 +72,31 @@ export const AsyncMethodComponent = React.memo((props: AsyncMethodProps) => {
const execute = async (event: React.FormEvent) => { const execute = async (event: React.FormEvent) => {
event.preventDefault(); event.preventDefault();
let method_name: string; let method_name: string;
const kwargs: Record<string, unknown> = {};
if (runningTask !== undefined && runningTask !== null) { if (runningTask !== undefined && runningTask !== null) {
method_name = `stop_${name}`; method_name = `stop_${name}`;
} else { } else {
Object.keys(props.parameters).forEach(
(name) => (kwargs[name] = event.target[name].value)
);
method_name = `start_${name}`; method_name = `start_${name}`;
} }
runMethod(method_name, parentPath, kwargs); runMethod(method_name, parentPath, {});
}; };
const args = Object.entries(props.parameters).map(([name, type], index) => {
const form_name = `${name} (${type})`;
const value = runningTask && runningTask[name];
const isRunning = value !== undefined && value !== null;
return (
<InputGroup key={index}>
<InputGroup.Text className="component-label">{form_name}</InputGroup.Text>
<Form.Control
type="text"
name={name}
defaultValue={isRunning ? value : ''}
disabled={isRunning}
/>
</InputGroup>
);
});
return ( return (
<div className="component asyncMethodComponent" id={id}> <div className="component asyncMethodComponent" id={id}>
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
<h5>Function: {displayName}</h5>
<Form onSubmit={execute} ref={formRef}> <Form onSubmit={execute} ref={formRef}>
{args} <InputGroup>
<Button id={`button-${id}`} type="submit"> <InputGroup.Text>
{runningTask ? 'Stop ' : 'Start '} {displayName}
<DocStringComponent docString={docString} /> <DocStringComponent docString={docString} />
</Button> </InputGroup.Text>
<Button id={`button-${id}`} type="submit">
{runningTask ? 'Stop ' : 'Start '}
</Button>
</InputGroup>
</Form> </Form>
</div> </div>
); );

View File

@ -1,144 +1,46 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef } from 'react';
import { runMethod } from '../socket'; import { runMethod } from '../socket';
import { Button, Form, Card } from 'react-bootstrap'; import { Button, Form } from 'react-bootstrap';
import { DocStringComponent } from './DocStringComponent'; import { DocStringComponent } from './DocStringComponent';
import { LevelName } from './NotificationsComponent'; import { LevelName } from './NotificationsComponent';
import { SerializedValue } from './GenericComponent';
import { FloatObject, NumberComponent, QuantityObject } from './NumberComponent';
import { StringComponent } from './StringComponent';
import { ColouredEnumComponent } from './ColouredEnumComponent';
import { EnumComponent } from './EnumComponent';
type MethodProps = { type MethodProps = {
name: string; name: string;
parentPath: string; parentPath: string;
parameters: Record<string, SerializedValue>;
docString?: string; docString?: string;
hideOutput?: boolean;
addNotification: (message: string, levelname?: LevelName) => void; addNotification: (message: string, levelname?: LevelName) => void;
displayName: string; displayName: string;
id: string; id: string;
render: boolean;
}; };
export const MethodComponent = React.memo((props: MethodProps) => { export const MethodComponent = React.memo((props: MethodProps) => {
const { name, parentPath, docString, addNotification, displayName, id } = props; const { name, parentPath, docString, addNotification, displayName, id } = props;
// Conditional rendering based on the 'render' prop.
if (!props.render) {
return null;
}
const renderCount = useRef(0); const renderCount = useRef(0);
// Add a new state variable to hold the list of function calls const formRef = useRef(null);
const [functionCalls, setFunctionCalls] = useState([]);
const fullAccessPath = [parentPath, name].filter((element) => element).join('.'); const fullAccessPath = [parentPath, name].filter((element) => element).join('.');
const triggerNotification = (args: Record<string, string>) => { const triggerNotification = () => {
const argsString = Object.entries(args) const message = `Method ${fullAccessPath} was triggered.`;
.map(([key, value]) => `${key}: "${value}"`)
.join(', ');
let message = `Method ${fullAccessPath} was triggered`;
if (argsString === '') {
message += '.';
} else {
message += ` with arguments {${argsString}}.`;
}
addNotification(message); addNotification(message);
}; };
const execute = async (event: React.FormEvent) => { const execute = async (event: React.FormEvent) => {
event.preventDefault(); event.preventDefault();
const kwargs = {}; runMethod(name, parentPath, {});
Object.keys(props.parameters).forEach((name) => {
kwargs[name] = event.target[name].value;
});
runMethod(name, parentPath, kwargs, (ack) => {
// Update the functionCalls state with the new call if we get an acknowledge msg
if (ack !== undefined) {
setFunctionCalls((prevCalls) => [
...prevCalls,
{ name, args: kwargs, result: ack }
]);
}
});
triggerNotification(kwargs); triggerNotification();
}; };
const args = Object.entries(props.parameters).map(([name, serializedValue]) => {
if (serializedValue.type == 'float' || serializedValue.type == 'int') {
return (
<NumberComponent
name={name}
value={(serializedValue as FloatObject).value}
displayName={name}
type={serializedValue.type}
readOnly={false}
docString={undefined}
isInstantUpdate={false}
addNotification={() => {}}
id={id + '.' + name}
/>
);
} else if (serializedValue.type == 'Quantity') {
return (
<NumberComponent
name={name}
value={(serializedValue as QuantityObject).value.magnitude}
displayName={name}
unit={(serializedValue as QuantityObject).value.unit}
type="float"
readOnly={false}
docString={undefined}
isInstantUpdate={false}
addNotification={() => {}}
id={id + '.' + name}
/>
);
} else if (serializedValue.type == 'str') {
return (
<StringComponent
name={name}
value={serializedValue.value as string}
displayName={name}
readOnly={false}
docString={undefined}
isInstantUpdate={false}
addNotification={() => {}}
id={id + '.' + name}
/>
);
} else if (serializedValue.type == 'Enum') {
return (
<EnumComponent
name={name}
parentPath={parentPath}
value={serializedValue.value as string}
readOnly={false}
docString={undefined}
addNotification={() => {}}
enumDict={serializedValue.enum}
displayName={name}
id={id + '.' + name}
/>
);
} else if (serializedValue.type == 'ColouredEnum') {
return (
<ColouredEnumComponent
name={name}
parentPath={parentPath}
value={serializedValue.value as string}
readOnly={false}
docString={undefined}
addNotification={() => {}}
enumDict={serializedValue.enum}
displayName={name}
id={id + '.' + name}
/>
);
}
});
// Content conditionally rendered based on args
const formContent = ( const formContent = (
<Form onSubmit={execute}> <Form onSubmit={execute} ref={formRef}>
{args}
<Button className="component" variant="primary" type="submit"> <Button className="component" variant="primary" type="submit">
{`${displayName} `} {`${displayName} `}
<DocStringComponent docString={docString} /> <DocStringComponent docString={docString} />
@ -146,22 +48,6 @@ export const MethodComponent = React.memo((props: MethodProps) => {
</Form> </Form>
); );
const outputContent = (
<div id="function-output">
{functionCalls.map((call, index) => (
<div key={index}>
<div style={{ color: 'grey', fontSize: 'small' }}>
{Object.entries(call.args)
.map(([key, val]) => `${key}=${JSON.stringify(val)}`)
.join(', ') +
' => ' +
JSON.stringify(call.result)}
</div>
</div>
))}
</div>
);
useEffect(() => { useEffect(() => {
renderCount.current++; renderCount.current++;
}); });
@ -171,16 +57,7 @@ export const MethodComponent = React.memo((props: MethodProps) => {
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<div>Render count: {renderCount.current}</div> <div>Render count: {renderCount.current}</div>
)} )}
{args.length > 0 ? ( <div>{formContent}</div>
<Card>
<Card.Body>
{formContent}
{outputContent}
</Card.Body>
</Card>
) : (
<div>{formContent}</div>
)}
</div> </div>
); );
}); });