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

View File

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

View File

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

View File

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

View File

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

View File

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