feat: adding instant_update switch

This commit is contained in:
Mose Müller 2023-08-02 12:06:22 +02:00
parent 80fe1051f1
commit 5cb30688cf
7 changed files with 80 additions and 28 deletions

View File

@ -1,4 +1,5 @@
import { useEffect, useReducer } from 'react';
import { useEffect, useReducer, useState } from 'react';
import { Form } from 'react-bootstrap';
import { hostname, port, socket } from './socket';
import {
DataServiceComponent,
@ -105,6 +106,7 @@ const reducer = (state: State, action: Action): State => {
const App = () => {
const [state, dispatch] = useReducer(reducer, null);
const [isInstantUpdate, setIsInstantUpdate] = useState(true);
// const [isConnected, setIsConnected] = useState(socket.connected);
useEffect(() => {
@ -134,9 +136,20 @@ const App = () => {
return <p>Loading...</p>;
}
return (
<>
<Form.Check
checked={isInstantUpdate}
onChange={(e) => setIsInstantUpdate(e.target.checked)}
type="switch"
/>
<div className="App">
<DataServiceComponent props={state as DataServiceJSON} />
<DataServiceComponent
props={state as DataServiceJSON}
isInstantUpdate={isInstantUpdate}
/>
</div>
</>
);
};

View File

@ -7,12 +7,13 @@ import { Attribute, GenericComponent } from './GenericComponent';
type DataServiceProps = {
props: DataServiceJSON;
parentPath?: string;
isInstantUpdate: boolean;
};
export type DataServiceJSON = Record<string, Attribute>;
export const DataServiceComponent = React.memo(
({ props, parentPath = 'DataService' }: DataServiceProps) => {
({ props, parentPath = 'DataService', isInstantUpdate }: DataServiceProps) => {
const [open, setOpen] = useState(true);
return (
@ -33,6 +34,7 @@ export const DataServiceComponent = React.memo(
attribute={value}
name={key}
parentPath={parentPath}
isInstantUpdate={isInstantUpdate}
/>
);
})}

View File

@ -34,10 +34,11 @@ type GenericComponentProps = {
attribute: Attribute;
name: string;
parentPath: string;
isInstantUpdate: boolean;
};
export const GenericComponent = React.memo(
({ attribute, name, parentPath }: GenericComponentProps) => {
({ attribute, name, parentPath, isInstantUpdate }: GenericComponentProps) => {
if (attribute.type === 'bool') {
return (
<ButtonComponent
@ -57,6 +58,7 @@ export const GenericComponent = React.memo(
docString={attribute.doc}
readOnly={attribute.readonly}
value={Number(attribute.value)}
isInstantUpdate={isInstantUpdate}
/>
);
} else if (attribute.type === 'NumberSlider') {
@ -70,6 +72,7 @@ export const GenericComponent = React.memo(
min={attribute.value['min']['value']}
max={attribute.value['max']['value']}
stepSize={attribute.value['step_size']['value']}
isInstantUpdate={isInstantUpdate}
/>
);
} else if (attribute.type === 'Enum') {
@ -111,6 +114,7 @@ export const GenericComponent = React.memo(
readOnly={attribute.readonly}
docString={attribute.doc}
parent_path={parentPath}
isInstantUpdate={isInstantUpdate}
/>
);
} else if (attribute.type === 'DataService') {
@ -118,15 +122,17 @@ export const GenericComponent = React.memo(
<DataServiceComponent
props={attribute.value as DataServiceJSON}
parentPath={parentPath.concat('.', name)}
isInstantUpdate={isInstantUpdate}
/>
);
} else if (attribute.type === 'list') {
return (
<ListComponent
name={name}
value={attribute.value as object[]}
value={attribute.value as Attribute[]}
docString={attribute.doc}
parent_path={parentPath}
isInstantUpdate={isInstantUpdate}
/>
);
} else {

View File

@ -1,12 +1,13 @@
import React, { useEffect, useRef } from 'react';
import { DocStringComponent } from './DocStringComponent';
import { GenericComponent } from './GenericComponent';
import { Attribute, GenericComponent } from './GenericComponent';
interface ListComponentProps {
name: string;
parent_path?: string;
value: object[];
value: Attribute[];
docString: string;
isInstantUpdate: boolean;
}
export const ListComponent = React.memo((props: ListComponentProps) => {
@ -16,7 +17,7 @@ export const ListComponent = React.memo((props: ListComponentProps) => {
renderCount.current++;
});
const { name, parent_path, value, docString } = props;
const { name, parent_path, value, docString, isInstantUpdate } = props;
return (
<div className={'component list'} id={parent_path.concat(name)}>
@ -31,6 +32,7 @@ export const ListComponent = React.memo((props: ListComponentProps) => {
attribute={item}
name={`${name}[${index}]`}
parentPath={parent_path}
isInstantUpdate={isInstantUpdate}
/>
);
})}

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import { Form, InputGroup, Button } from 'react-bootstrap';
import { socket } from '../socket';
import { emit_update } from '../socket';
import { DocStringComponent } from './DocStringComponent';
// TODO: add button functionality
@ -12,6 +12,7 @@ interface NumberComponentProps {
value: number;
readOnly: boolean;
docString: string;
isInstantUpdate: boolean;
}
// TODO: highlight the digit that is being changed by setting both selectionStart and
@ -98,7 +99,7 @@ const handleDeleteKey = (
};
export const NumberComponent = React.memo((props: NumberComponentProps) => {
const { name, parent_path, readOnly, docString } = props;
const { name, parent_path, readOnly, docString, isInstantUpdate } = props;
const renderCount = useRef(0);
// Create a state for the cursor position
const [cursorPosition, setCursorPosition] = useState(null);
@ -114,7 +115,7 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
// Update inputString only when value is different from numericInputString
// preventing the removal of trailing decimals or zeros after the decimal.
if (props.value !== numericInputString) {
if (isInstantUpdate && props.value !== numericInputString) {
setInputString(props.value.toString());
}
@ -195,17 +196,18 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
selectionStart,
selectionEnd
));
} else if (key === 'Enter' && !isInstantUpdate) {
emit_update(name, parent_path, Number(newValue));
return;
} else {
console.debug(key);
return;
}
// Update the input value and maintain the cursor position
socket.emit('frontend_update', {
name: name,
parent_path: parent_path,
value: Number(newValue)
});
if (isInstantUpdate) {
emit_update(name, parent_path, Number(newValue));
}
setInputString(newValue);
@ -213,6 +215,13 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
setCursorPosition(selectionStart);
};
const handleBlur = () => {
if (!isInstantUpdate) {
// If not in "instant update" mode, emit an update when the input field loses focus
emit_update(name, parent_path, Number(inputString));
}
};
return (
<div className={'number component'} id={parent_path.concat(name)}>
{process.env.NODE_ENV === 'development' && (
@ -229,6 +238,7 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
disabled={readOnly}
name={parent_path.concat(name)}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
/>
</InputGroup>
{!readOnly && (

View File

@ -13,6 +13,7 @@ interface SliderComponentProps {
readOnly: boolean;
docString: string;
stepSize: number;
isInstantUpdate: boolean;
}
export const SliderComponent = React.memo((props: SliderComponentProps) => {

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { Form, InputGroup } from 'react-bootstrap';
import { socket } from '../socket';
import { emit_update } from '../socket';
import { DocStringComponent } from './DocStringComponent';
// TODO: add button functionality
@ -11,22 +11,38 @@ interface StringComponentProps {
value: string;
readOnly: boolean;
docString: string;
isInstantUpdate: boolean;
}
export const StringComponent = React.memo((props: StringComponentProps) => {
const renderCount = useRef(0);
const [inputString, setInputString] = useState(props.value);
const { name, parent_path, readOnly, docString, isInstantUpdate } = props;
useEffect(() => {
renderCount.current++;
});
if (isInstantUpdate && props.value !== inputString) {
setInputString(props.value);
}
}, [isInstantUpdate, props.value, inputString, renderCount]);
const { name, parent_path, value, readOnly, docString } = props;
const handleChange = (event) => {
socket.emit('frontend_update', {
name: name,
parent_path: parent_path,
value: event.target.value
});
setInputString(event.target.value);
if (isInstantUpdate) {
emit_update(name, parent_path, event.target.value);
}
};
const handleKeyDown = (event) => {
if (event.key === 'Enter' && !isInstantUpdate) {
emit_update(name, parent_path, inputString);
}
};
const handleBlur = () => {
if (!isInstantUpdate) {
emit_update(name, parent_path, inputString);
}
};
return (
@ -41,10 +57,12 @@ export const StringComponent = React.memo((props: StringComponentProps) => {
<InputGroup.Text>{name}</InputGroup.Text>
<Form.Control
type="text"
value={value}
value={inputString}
disabled={readOnly}
name={name}
onChange={handleChange}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
/>
</InputGroup>
</div>