From 5cb30688cf786c1e8dde1f0783b9a8cc2e6bbe11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Wed, 2 Aug 2023 12:06:22 +0200 Subject: [PATCH] feat: adding instant_update switch --- frontend/src/App.tsx | 21 ++++++++-- .../src/components/DataServiceComponent.tsx | 4 +- frontend/src/components/GenericComponent.tsx | 10 ++++- frontend/src/components/ListComponent.tsx | 8 ++-- frontend/src/components/NumberComponent.tsx | 26 +++++++++---- frontend/src/components/SliderComponent.tsx | 1 + frontend/src/components/StringComponent.tsx | 38 ++++++++++++++----- 7 files changed, 80 insertions(+), 28 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3015e41..f802c8b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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

Loading...

; } return ( -
- -
+ <> + setIsInstantUpdate(e.target.checked)} + type="switch" + /> + +
+ +
+ ); }; diff --git a/frontend/src/components/DataServiceComponent.tsx b/frontend/src/components/DataServiceComponent.tsx index ae386d5..69b4cb3 100644 --- a/frontend/src/components/DataServiceComponent.tsx +++ b/frontend/src/components/DataServiceComponent.tsx @@ -7,12 +7,13 @@ import { Attribute, GenericComponent } from './GenericComponent'; type DataServiceProps = { props: DataServiceJSON; parentPath?: string; + isInstantUpdate: boolean; }; export type DataServiceJSON = Record; 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} /> ); })} diff --git a/frontend/src/components/GenericComponent.tsx b/frontend/src/components/GenericComponent.tsx index 3f1163e..5f404e3 100644 --- a/frontend/src/components/GenericComponent.tsx +++ b/frontend/src/components/GenericComponent.tsx @@ -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 ( ); } 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( ); } else if (attribute.type === 'list') { return ( ); } else { diff --git a/frontend/src/components/ListComponent.tsx b/frontend/src/components/ListComponent.tsx index bb50071..3b4ca07 100644 --- a/frontend/src/components/ListComponent.tsx +++ b/frontend/src/components/ListComponent.tsx @@ -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 (
@@ -31,6 +32,7 @@ export const ListComponent = React.memo((props: ListComponentProps) => { attribute={item} name={`${name}[${index}]`} parentPath={parent_path} + isInstantUpdate={isInstantUpdate} /> ); })} diff --git a/frontend/src/components/NumberComponent.tsx b/frontend/src/components/NumberComponent.tsx index cf937c9..c4c9a8a 100644 --- a/frontend/src/components/NumberComponent.tsx +++ b/frontend/src/components/NumberComponent.tsx @@ -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 (
{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} /> {!readOnly && ( diff --git a/frontend/src/components/SliderComponent.tsx b/frontend/src/components/SliderComponent.tsx index ab39a76..871a696 100644 --- a/frontend/src/components/SliderComponent.tsx +++ b/frontend/src/components/SliderComponent.tsx @@ -13,6 +13,7 @@ interface SliderComponentProps { readOnly: boolean; docString: string; stepSize: number; + isInstantUpdate: boolean; } export const SliderComponent = React.memo((props: SliderComponentProps) => { diff --git a/frontend/src/components/StringComponent.tsx b/frontend/src/components/StringComponent.tsx index 447d992..2e7b0b2 100644 --- a/frontend/src/components/StringComponent.tsx +++ b/frontend/src/components/StringComponent.tsx @@ -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) => { {name}