From 63c61213d2e95545706eba41e2d286ebca6c9726 Mon Sep 17 00:00:00 2001 From: Axel Bocciarelli Date: Tue, 1 Oct 2024 16:18:39 +0200 Subject: [PATCH] Improve beamline attribute controls --- ui/package.json | 1 - ui/pnpm-lock.yaml | 84 ++------- .../BeamlineAttribute/BeamlineAttribute.jsx | 82 +++++++++ .../BeamlineAttribute.module.css | 26 +++ .../BeamlineAttributeForm.jsx | 63 +++++++ .../components/LabeledValue/LabeledValue.jsx | 66 ------- ui/src/components/LabeledValue/style.css | 10 -- .../MachInfo/machineInfoStyle.module.css | 11 -- ui/src/components/PopInput/NumericInput.jsx | 64 ------- ui/src/components/PopInput/PopInput.jsx | 164 ------------------ ui/src/components/PopInput/style.css | 47 ----- .../components/SampleGrid/SampleGridTable.css | 2 +- ui/src/components/SampleView/SampleView.css | 4 +- ui/src/containers/BeamlineSetupContainer.jsx | 47 ++--- ui/src/main.css | 97 ----------- 15 files changed, 204 insertions(+), 564 deletions(-) create mode 100644 ui/src/components/BeamlineAttribute/BeamlineAttribute.jsx create mode 100644 ui/src/components/BeamlineAttribute/BeamlineAttribute.module.css create mode 100644 ui/src/components/BeamlineAttribute/BeamlineAttributeForm.jsx delete mode 100644 ui/src/components/LabeledValue/LabeledValue.jsx delete mode 100644 ui/src/components/LabeledValue/style.css delete mode 100644 ui/src/components/PopInput/NumericInput.jsx delete mode 100644 ui/src/components/PopInput/PopInput.jsx delete mode 100644 ui/src/components/PopInput/style.css diff --git a/ui/package.json b/ui/package.json index 1f406be7d..cbd51f71c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -33,7 +33,6 @@ "immutability-helper": "^3.0.2", "isomorphic-fetch": "^2.2.0", "lodash": "^4.17.21", - "rc-input-number": "8.0.3", "react": "^17.0.2", "react-bootstrap": "2.8.0", "react-chat-widget": "^3.1.4", diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index b9e09a84d..5d113ea9b 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -50,9 +50,6 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 - rc-input-number: - specifier: 8.0.3 - version: 8.0.3(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: specifier: ^17.0.2 version: 17.0.2 @@ -592,10 +589,6 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@rc-component/mini-decimal@1.1.0': - resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} - engines: {node: '>=8.x'} - '@react-aria/ssr@3.6.0': resolution: {integrity: sha512-OFiYQdv+Yk7AO7IsQu/fAEPijbeTwrrEYvdNoJ3sblBBedD5j5fBTNWrUPNVlwC4XWWnWTCMaRIVsJujsFiWXg==} peerDependencies: @@ -3440,24 +3433,6 @@ packages: resolution: {integrity: sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==} engines: {node: '>= 0.10.0'} - rc-input-number@8.0.3: - resolution: {integrity: sha512-GHfWvufXEmwF/wtR8oPZNTuMdFb/rvx/+Sp2bZfaPftM+LFFdO8o3/PaeTk8DKt0Tv+u5Zuf68lqLdGCkmAXRg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-input@1.1.0: - resolution: {integrity: sha512-izuNXPABQPh4KD7ANFcTrIGp9EZU0FkjTw6AvwCQ/rGPrdDsUTHLsp/Wju/kzGMLJFJWKNF3smbmXRNO23DtXA==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - rc-util@5.34.1: - resolution: {integrity: sha512-SqiUT8Ssgh5C+hu4y887xwCrMNcxLm6ScOo8AFlWYYF3z9uNNiPpwwSjvicqOlWd79rNw1g44rnP7tz9MrO1ZQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - react-bootstrap@2.8.0: resolution: {integrity: sha512-e/aNtxl0Z2ozrIaR82jr6Zz7ss9GSoaXpQaxmvtDUsTZIq/XalkduR/ZXP6vbQHz2T4syvjA+4FbtwELxxmpww==} peerDependencies: @@ -4910,10 +4885,6 @@ snapshots: '@popperjs/core@2.11.8': {} - '@rc-component/mini-decimal@1.1.0': - dependencies: - '@babel/runtime': 7.22.3 - '@react-aria/ssr@3.6.0(react@17.0.2)': dependencies: '@swc/helpers': 0.4.14 @@ -8075,28 +8046,28 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss-import@15.1.0(postcss@8.4.41): + postcss-import@15.1.0(postcss@8.4.47): dependencies: - postcss: 8.4.41 + postcss: 8.4.47 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.41): + postcss-js@4.0.1(postcss@8.4.47): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.41 + postcss: 8.4.47 - postcss-load-config@4.0.2(postcss@8.4.41): + postcss-load-config@4.0.2(postcss@8.4.47): dependencies: lilconfig: 3.1.2 yaml: 2.5.0 optionalDependencies: - postcss: 8.4.41 + postcss: 8.4.47 - postcss-nested@6.2.0(postcss@8.4.41): + postcss-nested@6.2.0(postcss@8.4.47): dependencies: - postcss: 8.4.41 + postcss: 8.4.47 postcss-selector-parser: 6.1.1 postcss-selector-parser@6.1.1: @@ -8179,31 +8150,6 @@ snapshots: kind-of: 6.0.3 math-random: 1.0.4 - rc-input-number@8.0.3(react-dom@17.0.2(react@17.0.2))(react@17.0.2): - dependencies: - '@babel/runtime': 7.22.3 - '@rc-component/mini-decimal': 1.1.0 - classnames: 2.3.2 - rc-input: 1.1.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) - rc-util: 5.34.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - - rc-input@1.1.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2): - dependencies: - '@babel/runtime': 7.22.3 - classnames: 2.3.2 - rc-util: 5.34.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - - rc-util@5.34.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2): - dependencies: - '@babel/runtime': 7.22.3 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-is: 16.13.1 - react-bootstrap@2.8.0(@types/react@17.0.60)(react-dom@17.0.2(react@17.0.2))(react@17.0.2): dependencies: '@babel/runtime': 7.22.3 @@ -8578,7 +8524,7 @@ snapshots: dependencies: chokidar: 3.5.3 immutable: 4.3.7 - source-map-js: 1.2.0 + source-map-js: 1.2.1 optional: true saxes@5.0.1: @@ -8911,12 +8857,12 @@ snapshots: micromatch: 4.0.5 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.41 - postcss-import: 15.1.0(postcss@8.4.41) - postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.2(postcss@8.4.41) - postcss-nested: 6.2.0(postcss@8.4.41) + picocolors: 1.1.0 + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47) + postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.1 resolve: 1.22.8 sucrase: 3.35.0 diff --git a/ui/src/components/BeamlineAttribute/BeamlineAttribute.jsx b/ui/src/components/BeamlineAttribute/BeamlineAttribute.jsx new file mode 100644 index 000000000..aa53df673 --- /dev/null +++ b/ui/src/components/BeamlineAttribute/BeamlineAttribute.jsx @@ -0,0 +1,82 @@ +import React, { useRef } from 'react'; +import { Button, OverlayTrigger, Popover } from 'react-bootstrap'; + +import { STATE } from '../../actions/beamline'; +import BeamlineAttributeForm from './BeamlineAttributeForm'; +import styles from './BeamlineAttribute.module.css'; +import '../input.css'; + +const STATE_CLASS = { + [STATE.BUSY]: 'input-bg-moving', + [STATE.ABORT]: 'input-bg-fault', +}; + +function BeamlineAttribute(props) { + const { attribute, format, precision = 1, suffix, onSave, onCancel } = props; + const { + state = STATE.IDLE, + value = 0, + step = 0.1, + msg, + readonly = false, + } = attribute; + + const btnRef = useRef(null); + + const valStr = value + ? format === 'expo' + ? value.toExponential(precision) + : value.toFixed(precision) + : '-'; + + if (readonly) { + return ( + + {valStr} + {suffix && ` ${suffix}`} + + ); + } + + return ( + { + if (!show) { + // Bring focus back on button when popover closes + btnRef.current?.focus({ preventScroll: true }); + } + }} + overlay={ + + + {msg &&
{msg}
} +
+ } + > + +
+ ); +} + +export default BeamlineAttribute; diff --git a/ui/src/components/BeamlineAttribute/BeamlineAttribute.module.css b/ui/src/components/BeamlineAttribute/BeamlineAttribute.module.css new file mode 100644 index 000000000..a2de248f8 --- /dev/null +++ b/ui/src/components/BeamlineAttribute/BeamlineAttribute.module.css @@ -0,0 +1,26 @@ +.value { + display: inline-flex; + padding: 0.5rem 0.25rem; + white-space: nowrap; +} + +.valueBtn { + composes: value; + font-weight: inherit; + text-decoration: none; +} + +.valueBtn:hover, +.valueBtn:focus-visible { + text-decoration: underline dotted; +} + +.valueBtn:focus-visible { + outline: auto !important; + outline-offset: -2px; +} + +.input { + height: 36px; + appearance: auto !important; +} diff --git a/ui/src/components/BeamlineAttribute/BeamlineAttributeForm.jsx b/ui/src/components/BeamlineAttribute/BeamlineAttributeForm.jsx new file mode 100644 index 000000000..a39552685 --- /dev/null +++ b/ui/src/components/BeamlineAttribute/BeamlineAttributeForm.jsx @@ -0,0 +1,63 @@ +import React, { useEffect, useRef } from 'react'; +import { Button, ButtonToolbar, Form } from 'react-bootstrap'; +import { TiTick, TiTimes } from 'react-icons/ti'; + +import styles from './BeamlineAttribute.module.css'; + +function BeamlineAttributeForm(props) { + const { value, isBusy, step, precision, onSave, onCancel } = props; + const inputRef = useRef(null); + + useEffect(() => { + if (!isBusy) { + setTimeout(() => { + /* Focus and select text when popover opens and every time a value is applied. + * Timeout ensures this works when opening a popover while another is already opened. */ + inputRef.current?.focus({ preventScroll: true }); + inputRef.current?.select(); + }, 0); + } + }, [isBusy]); + + function handleSubmit(evt) { + evt.preventDefault(); + const formData = new FormData(evt.target); + const strVal = formData.get('value'); + + const numVal = + typeof strVal === 'string' ? Number.parseFloat(strVal) : Number.NaN; + + if (!Number.isNaN(numVal)) { + onSave(numVal); + } + } + + return ( +
+ + + {isBusy ? ( + + ) : ( + + )} + +
+ ); +} + +export default BeamlineAttributeForm; diff --git a/ui/src/components/LabeledValue/LabeledValue.jsx b/ui/src/components/LabeledValue/LabeledValue.jsx deleted file mode 100644 index 9f30780de..000000000 --- a/ui/src/components/LabeledValue/LabeledValue.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { Form } from 'react-bootstrap'; - -import './style.css'; - -export default class LabeledValue extends React.Component { - render() { - let labelStyle = { - backgroundColor: 'transparent', - display: 'block', - marginBottom: 0, - color: '#000', - }; - let valueStyle = { - backgroundColor: 'transparent', - display: 'block-inline', - fontSize: '100%', - borderRadius: 0, - color: '#000', - padding: 0, - marginBottom: 0, - whiteSpace: 'nowrap', - }; - - if (this.props.look === 'vertical') { - labelStyle = { display: 'block', marginBottom: '3px' }; - valueStyle = { display: 'block', fontSize: '100%', borderRadius: 0 }; - } - - let value = this.props.value.toFixed( - Number.parseInt(this.props.precision, 10), - ); - - if (this.props.format === 'expo') { - value = Number.parseFloat(this.props.value).toExponential( - this.props.precision, - ); - } - - return ( -
- {this.props.name && ( - - {this.props.name} - - - )} - - - {value} {this.props.suffix} - -
- ); - } -} - -LabeledValue.defaultProps = { - extraInfo: {}, - value: 0, - name: '', - suffix: '', - precision: '1', - format: '', - look: 'horizontal', - level: 'info', -}; diff --git a/ui/src/components/LabeledValue/style.css b/ui/src/components/LabeledValue/style.css deleted file mode 100644 index 4d7fb3860..000000000 --- a/ui/src/components/LabeledValue/style.css +++ /dev/null @@ -1,10 +0,0 @@ -.current-label { - font-size: 18px; - text-align: center; -} - -.current-value { - padding: 10px; - font-weight: bold; - color: blue; -} diff --git a/ui/src/components/MachInfo/machineInfoStyle.module.css b/ui/src/components/MachInfo/machineInfoStyle.module.css index 80298d8c8..fb0980a34 100644 --- a/ui/src/components/MachInfo/machineInfoStyle.module.css +++ b/ui/src/components/MachInfo/machineInfoStyle.module.css @@ -1,14 +1,3 @@ -.current-label { - font-size: 18px; - text-align: center; -} - -.current-value { - padding: 10px; - font-weight: bold; - color: blue; -} - .machine-msg-attention { font-size: 12px; font-style: italic; diff --git a/ui/src/components/PopInput/NumericInput.jsx b/ui/src/components/PopInput/NumericInput.jsx deleted file mode 100644 index ebf9773b0..000000000 --- a/ui/src/components/PopInput/NumericInput.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useState } from 'react'; -import { Form, Button, ButtonToolbar } from 'react-bootstrap'; -import RcInputNumber from 'rc-input-number'; -import { TiTick, TiTimes } from 'react-icons/ti'; - -function NumericInput(props) { - const { initialValue, precision, step, size, busy, onCancel, onSubmit } = - props; - - const [value, setValue] = useState(initialValue); - - function handleSubmit(evt) { - evt.preventDefault(); - - if (!busy) { - onSubmit(value); - } - } - - return ( -
-