From 65f55902ce64900b514a5a2cd0cdb81863df0c43 Mon Sep 17 00:00:00 2001 From: Anthony Geourjon <anthony.geourjon@tetras-libre.fr> Date: Fri, 29 Dec 2023 17:13:57 +0100 Subject: [PATCH] MigratingAnnotationCreation to MUI5. It's possible to display the creation annotation window, not validate the creation. --- src/AnnotationCreation.js | 6 +- src/HMSInput.js | 168 +++++++++++++++----------------------- src/ImageFormField.js | 84 ++++++++----------- 3 files changed, 105 insertions(+), 153 deletions(-) diff --git a/src/AnnotationCreation.js b/src/AnnotationCreation.js index 062ac07..c1ab3dc 100644 --- a/src/AnnotationCreation.js +++ b/src/AnnotationCreation.js @@ -538,7 +538,7 @@ class AnnotationCreation extends Component { <Alarm fontSize="small" /> </ToggleButton> </div> - {/* <HMSInput seconds={tstart} onChange={this.updateTstart} /> */} + <HMSInput seconds={tstart} onChange={this.updateTstart} /> </div> <div style={{ border: '1px solid rgba(0, 0, 0, 0.12)', @@ -580,7 +580,7 @@ class AnnotationCreation extends Component { <Alarm fontSize="small" /> </ToggleButton> </div> - {/* <HMSInput seconds={tend} onChange={this.updateTend} /> */} + <HMSInput seconds={tend} onChange={this.updateTend} /> </div> </div> </> @@ -594,7 +594,7 @@ class AnnotationCreation extends Component { </Typography> </Grid> <Grid item xs={12} style={{ marginBottom: 10 }}> - {/* <ImageFormField value={image} onChange={this.handleImgChange} /> */} + <ImageFormField value={image} onChange={this.handleImgChange} /> </Grid> </Grid> </div> diff --git a/src/HMSInput.js b/src/HMSInput.js index ef04452..8270c58 100644 --- a/src/HMSInput.js +++ b/src/HMSInput.js @@ -1,117 +1,81 @@ -import React, { Component } from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@mui/material/styles'; -import { Input } from '@mui/material'; +import { Input, styled } from '@mui/material'; import { secondsToHMSarray } from './utils'; -/** hh:mm:ss input which behave like a single input for parent */ -class HMSInput extends Component { - /** Initialize state structure & bindings */ - constructor(props) { - super(props); +const StyledInput = styled(Input)(({ theme }) => ({ + height: 'fit-content', + margin: '2px', + '& input[type=number]': { + '-moz-appearance': 'textfield', + }, + '& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button': { + '-webkit-appearance': 'none', + margin: 0, + }, +})); - // eslint-disable-next-line react/destructuring-assignment - const [h, m, s] = secondsToHMSarray(this.props.seconds); - this.state = { - hours: h, - minutes: m, - seconds: s, - }; - this.someChange = this.someChange.bind(this); - } +const StyledHMSLabel = styled('span')({ + color: 'grey', +}); - /** update */ - componentDidUpdate(prevProps) { - const { seconds } = this.props; - if (prevProps.seconds === seconds) return; - const [h, m, s] = secondsToHMSarray(seconds); - this.setState({ - hours: h, - minutes: m, - seconds: s, - }); - } +const StyledRoot = styled('div')({ + alignItems: 'center', + display: 'flex', +}); - /** If one value is updated, tell the parent component the total seconds counts */ - someChange(ev) { - const { onChange } = this.props; - const { state } = this; - state[ev.target.name] = Number(ev.target.value); - onChange(state.hours * 3600 + state.minutes * 60 + state.seconds); - } +function HMSInput({ seconds, onChange }) { + const [hms, setHms] = useState(secondsToHMSarray(seconds)); - /** Render */ - render() { - const { hours, minutes, seconds } = this.state; - const { classes } = this.props; - return ( - <div className={classes.root}> - <div className={classes.root}> - <Input - className={classes.input} - variant="filled" - type="number" - min="0" - pattern - name="hours" - value={hours} - onChange={this.someChange} - inputProps={{ style: { textAlign: 'center' } }} - /> - <span className={classes.hmsLabel}>h</span> - <Input className={classes.input} type="number" min="0" max="59" name="minutes" value={minutes} onChange={this.someChange} inputProps={{ style: { textAlign: 'center' } }} /> - <span className={classes.hmsLabel}>m</span> - <Input className={classes.input} type="number" min="0" max="59" name="seconds" value={seconds} onChange={this.someChange} inputProps={{ style: { textAlign: 'center' } }} /> - <span className={classes.hmsLabel}>s</span> - </div> - </div> - ); - } -} + useEffect(() => { + setHms(secondsToHMSarray(seconds)); + }, [seconds]); -/** */ -const styles = (theme) => ({ - root: { - alignItems: 'center', - display: 'flex', - }, - // eslint-disable-next-line sort-keys - flexcol: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - }, - hmsLabel: { - color: 'grey', - }, - // eslint-disable-next-line sort-keys - input: { - height: 'fit-content', - margin: '2px', - // remove arrow from field for Firefox - '& input[type=number]': { - '-moz-appearance': 'textfield', - }, - // remove arrow from field for Chrome, Safari and Opera - '& input[type=number]::-webkit-outer-spin-button': { - '-webkit-appearance': 'none', - margin: 0, - }, - '& input[type=number]::-webkit-inner-spin-button': { - '-webkit-appearance': 'none', - margin: 0, - }, - }, -}); + const someChange = (ev) => { + const newState = { ...hms, [ev.target.name]: Number(ev.target.value) }; + setHms(newState); + onChange(newState.hours * 3600 + newState.minutes * 60 + newState.seconds); + }; + + return ( + <StyledRoot> + <StyledInput + variant="filled" + type="number" + min="0" + name="hours" + value={hms.hours} + onChange={someChange} + inputProps={{ style: { textAlign: 'center' } }} + /> + <StyledHMSLabel>h</StyledHMSLabel> + <StyledInput + type="number" + min="0" + max="59" + name="minutes" + value={hms.minutes} + onChange={someChange} + inputProps={{ style: { textAlign: 'center' } }} + /> + <StyledHMSLabel>m</StyledHMSLabel> + <StyledInput + type="number" + min="0" + max="59" + name="seconds" + value={hms.seconds} + onChange={someChange} + inputProps={{ style: { textAlign: 'center' } }} + /> + <StyledHMSLabel>s</StyledHMSLabel> + </StyledRoot> + ); +} HMSInput.propTypes = { - // eslint-disable-next-line react/forbid-prop-types - classes: PropTypes.object.isRequired, onChange: PropTypes.func.isRequired, seconds: PropTypes.number.isRequired, }; -HMSInput.defaultProps = { -}; - export default HMSInput; diff --git a/src/ImageFormField.js b/src/ImageFormField.js index 12ab29c..5fe3d13 100644 --- a/src/ImageFormField.js +++ b/src/ImageFormField.js @@ -1,62 +1,50 @@ -import React, { Component } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@mui/material/styles'; -import { TextField } from '@mui/material'; +import { TextField, styled } from '@mui/material'; -/** URL input with an <img> preview */ -class ImageFormField extends Component { - /** */ - constructor(props) { - super(props); +const StyledRoot = styled('div')(({ theme }) => ({ + alignItems: 'center', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', +})); - this.inputRef = React.createRef(); - } +function ImageFormField({ value: image, onChange }) { + const inputRef = useRef(null); + const [imgIsValid, setImgIsValid] = useState(false); - /** Render input field and a preview if the input is valid */ - render() { - const { value: image, classes, onChange } = this.props; - const imgIsValid = this.inputRef.current - ? (image.id && this.inputRef.current.checkValidity()) : image.id; - const imgUrl = image.id === null ? '' : image.id; - return ( - <div className={classes.root}> - <TextField - value={imgUrl} - onChange={(ev) => onChange(ev.target.value)} - error={imgUrl !== '' && !imgIsValid} - margin="dense" - label="Image URL" - type="url" - fullWidth - inputRef={this.inputRef} - /> - { imgIsValid - && <img src={image.id} width="100%" height="auto" alt="loading failed" /> } - </div> - ); - } -} + useEffect(() => { + if (inputRef.current) { + setImgIsValid(image.id && inputRef.current.checkValidity()); + } else { + setImgIsValid(!!image.id); + } + }, [image]); + + const imgUrl = image.id === null ? '' : image.id; -/** custom css */ -const styles = (theme) => ({ - root: { - alignItems: 'center', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - }, -}); + return ( + <StyledRoot> + <TextField + value={imgUrl} + onChange={(ev) => onChange(ev.target.value)} + error={imgUrl !== '' && !imgIsValid} + margin="dense" + label="Image URL" + type="url" + fullWidth + inputRef={inputRef} + /> + {imgIsValid && <img src={image.id} width="100%" height="auto" alt="loading failed" />} + </StyledRoot> + ); +} ImageFormField.propTypes = { - // eslint-disable-next-line react/forbid-prop-types - classes: PropTypes.object.isRequired, onChange: PropTypes.func.isRequired, value: PropTypes.shape({ id: PropTypes.string, }).isRequired, }; -ImageFormField.defaultProps = { -}; - export default ImageFormField; -- GitLab