import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react' import { useTranslation } from 'next-i18next' import { isDegreeMeasurement } from '../../../config/measurements' import { measurementAsMm } from 'shared/utils.mjs' /* * This is a single input for a measurements * Note that it keeps local state with whatever the user types * but will only trigger a gist update if the input is valid. * * m holds the measurement name. It's just so long to type * measurement and I always have some typo in it because dyslexia. */ export const MeasurementInput = ({ m, gist, app, updateMeasurements, focus }) => { const { t } = useTranslation(['app', 'measurements']) const prefix = app.site === 'org' ? '' : 'https://freesewing.org' const title = t(`measurements:${m}`) const isDegree = isDegreeMeasurement(m) const factor = useMemo(() => (isDegree ? 1 : gist.units == 'imperial' ? 25.4 : 10), [gist.units]) const isValValid = (val) => typeof val === 'undefined' || val === '' ? null : val != false && !isNaN(val) const isValid = (newVal) => (typeof newVal === 'undefined' ? isValValid(val) : isValValid(newVal)) const [val, setVal] = useState(gist.measurements?.[m] / factor || '') // keep a single reference to a debounce timer const debounceTimeout = useRef(null) const input = useRef(null) // onChange const update = useCallback( (evt) => { evt.stopPropagation() let evtVal = evt.target.value // set Val immediately so that the input reflects it setVal(evtVal) let useVal = isDegree ? evtVal : measurementAsMm(evtVal, gist.units) const ok = isValid(useVal) // only set to the gist if it's valid if (ok) { // debounce in case it's still changing if (debounceTimeout.current !== null) { clearTimeout(debounceTimeout.current) } debounceTimeout.current = setTimeout(() => { // clear the timeout reference debounceTimeout.current = null updateMeasurements(useVal, m) }, 500) } }, [gist.units] ) // use this for better update efficiency // FIXME: This breaks gist updates. // See: https://github.com/freesewing/freesewing/issues/2281 const memoVal = useMemo(() => gist.measurements?.[m], [gist]) // track validity against the value and the units const valid = useMemo( () => isValid(isDegree ? val : measurementAsMm(val, gist.units)), [val, gist.units] ) // hook to update the value or format when the gist changes useEffect(() => { // set the value to the proper value and format if (memoVal) { let gistVal = +(memoVal / factor).toFixed(2) setVal(gistVal) } }, [memoVal, factor]) // focus when prompted by parent useEffect(() => { if (focus) { input.current.focus() } }, [focus]) // cleanup useEffect(() => { clearTimeout(debounceTimeout.current) }, []) if (!m) return null return (