mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-04-20 00:10:03 +02:00
feat: adding instant_update switch
This commit is contained in:
parent
80fe1051f1
commit
5cb30688cf
@ -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 (
|
||||||
<div className="App">
|
<>
|
||||||
<DataServiceComponent props={state as DataServiceJSON} />
|
<Form.Check
|
||||||
</div>
|
checked={isInstantUpdate}
|
||||||
|
onChange={(e) => setIsInstantUpdate(e.target.checked)}
|
||||||
|
type="switch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="App">
|
||||||
|
<DataServiceComponent
|
||||||
|
props={state as DataServiceJSON}
|
||||||
|
isInstantUpdate={isInstantUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -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 {
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -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 && (
|
||||||
|
@ -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) => {
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user