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:
Mose Müller
2023-08-10 15:07:09 +02:00
parent 8205e4d463
commit f7579c3a89
21 changed files with 235 additions and 167 deletions

View File

@ -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];

View File

@ -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);

View File

@ -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}
/>
);
})}

View File

@ -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);

View File

@ -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 {

View File

@ -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>

View File

@ -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}
/>
);
})}

View File

@ -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})`;

View File

@ -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) => {

View File

@ -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">

View File

@ -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) => {