diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8c2ef7e..1dde597 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "bootstrap": "^5.3.0", "react": "^18.2.0", "react-bootstrap": "^2.8.0", + "react-bootstrap-range-slider": "^3.0.8", "react-dom": "^18.2.0", "react-scripts": "5.0.1", "socket.io-client": "^4.7.1", @@ -14403,6 +14404,20 @@ } } }, + "node_modules/react-bootstrap-range-slider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/react-bootstrap-range-slider/-/react-bootstrap-range-slider-3.0.8.tgz", + "integrity": "sha512-FpDd1J1BW23jNN3fXmpy5nNDJ3PwMZ2/0dNse9RORwQ/z2rmpMQp/g6iNRpW6SjQkLKyGeNHyctK6dP+3zUXQA==", + "dependencies": { + "classnames": "^2.3.1", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-bootstrap": ">=1.0.0", + "react-dom": ">=17.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -16459,16 +16474,16 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { diff --git a/frontend/package.json b/frontend/package.json index 4eb8a39..7e46a5b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "bootstrap": "^5.3.0", "react": "^18.2.0", "react-bootstrap": "^2.8.0", + "react-bootstrap-range-slider": "^3.0.8", "react-dom": "^18.2.0", "react-scripts": "5.0.1", "socket.io-client": "^4.7.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a65a802..32187fa 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,8 +3,16 @@ import { Component, ComponentLabel } from './components/component'; import { ButtonComponent } from './components/ButtonComponent'; import { socket } from './socket'; import { NumberComponent } from './components/NumberComponent'; +import { SliderComponent } from './components/SliderComponent'; -type AttributeType = 'str' | 'bool' | 'float' | 'int' | 'method' | 'Subclass'; +type AttributeType = + | 'str' + | 'bool' + | 'float' + | 'int' + | 'method' + | 'Subclass' + | 'NumberSlider'; type ValueType = boolean | string | number | object; interface Attribute { @@ -145,6 +153,21 @@ const App = () => { /> ); + } else if (value.type === 'NumberSlider') { + return ( +
+ +
+ ); } else if (!value.async) { return (
diff --git a/frontend/src/components/SliderComponent.tsx b/frontend/src/components/SliderComponent.tsx new file mode 100644 index 0000000..5294171 --- /dev/null +++ b/frontend/src/components/SliderComponent.tsx @@ -0,0 +1,142 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { + OverlayTrigger, + Badge, + Tooltip, + InputGroup, + Form, + Stack, + Row, + Col, + SplitButton, + Dropdown, + Button, + Collapse +} from 'react-bootstrap'; +import { socket } from '../socket'; +import './NumberComponent.css'; +import RangeSlider from 'react-bootstrap-range-slider'; + +interface SliderComponentProps { + name: string; + min: number; + max: number; + parent_path?: string; + value: number; + readOnly: boolean; + docString: string; + stepSize: number; +} + +export const SliderComponent = React.memo((props: SliderComponentProps) => { + const renderCount = useRef(0); + const [open, setOpen] = useState(false); + + useEffect(() => { + renderCount.current++; + }); + + const { name, parent_path, value, readOnly, docString } = props; + const [min, setMin] = useState(props.min); + const [max, setMax] = useState(props.max); + const [stepSize, setStepSize] = useState(props.stepSize); + + const tooltip = {docString}; + + const socketEmit = ( + newNumber: number, + min: number = props.min, + max: number = props.max, + stepSize: number = props.stepSize + ) => { + socket.emit('frontend_update', { + name: name, + value: { value: newNumber, min: min, max: max, step_size: stepSize } + }); + }; + const handleOnChange = (event, newNumber: number) => { + socketEmit(newNumber, min, max, stepSize); + }; + + const handleValueChange = (newValue: number, valueType: string) => { + switch (valueType) { + case 'min': + setMin(newValue); + break; + case 'max': + setMax(newValue); + break; + case 'stepSize': + setStepSize(newValue); + break; + default: + break; + } + socketEmit(value, min, max, stepSize); + }; + + return ( +
+

Render count: {renderCount.current}

+ + + {name} + + handleOnChange(event, newNumber)} + min={min} + max={max} + step={stepSize} + tooltip={'off'} + /> + + + + + + + +
+ + Min Value + handleValueChange(Number(e.target.value), 'min')} + /> + + Max Value + handleValueChange(Number(e.target.value), 'max')} + /> + + Step Size + handleValueChange(Number(e.target.value), 'stepSize')} + /> + +
+
+
+ + {docString && ( + + + ? + + + )} +
+ ); +});